Be context-agnostic in the effect Create functions

This allows the effect Update functions to handle the playback frequency being
changed. By default the effects assume a maximum frequency of 192khz, however,
it can go higher at the cost of the sample buffers being cleared and the risk
of an abort() if reallocation fails
This commit is contained in:
Chris Robinson 2009-10-19 07:46:53 -07:00
parent a4e3ca933b
commit 6d1d61026d
4 changed files with 171 additions and 96 deletions

View File

@ -29,6 +29,10 @@
#include "alError.h"
#include "alu.h"
// Just a soft maximum. Being higher will cause EchoUpdate to reallocate the
// sample buffer which may cause an abort if realloc fails
#define MAX_ECHO_FREQ 192000
typedef struct ALechoState {
// Must be first in all effects!
ALeffectState state;
@ -36,11 +40,12 @@ typedef struct ALechoState {
ALfloat *SampleBuffer;
ALuint BufferLength;
// The echo is two tap. The third tap is the offset to write the feedback
// and input sample to
// The echo is two tap. The delay is the number of samples from before the
// current offset
struct {
ALuint offset;
} Tap[3];
ALuint delay;
} Tap[2];
ALuint Offset;
// The LR gains for the first tap. The second tap uses the reverse
ALfloat GainL;
ALfloat GainR;
@ -80,19 +85,35 @@ ALvoid EchoDestroy(ALeffectState *effect)
}
}
ALvoid EchoUpdate(ALeffectState *effect, ALCcontext *Context, ALeffect *Effect)
ALvoid EchoUpdate(ALeffectState *effect, ALCcontext *Context, const ALeffect *Effect)
{
ALechoState *state = (ALechoState*)effect;
ALuint newdelay1, newdelay2;
ALfloat lrpan, cw, a, g;
ALuint maxlen;
newdelay1 = (ALuint)(Effect->Echo.Delay * Context->Frequency);
newdelay2 = (ALuint)(Effect->Echo.LRDelay * Context->Frequency);
maxlen = (ALuint)(AL_ECHO_MAX_DELAY * Context->Frequency);
maxlen += (ALuint)(AL_ECHO_MAX_LRDELAY * Context->Frequency);
maxlen = NextPowerOf2(maxlen+1);
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;
if(maxlen > state->BufferLength)
{
void *temp;
ALuint i;
state->BufferLength = maxlen;
temp = realloc(state->SampleBuffer, state->BufferLength * sizeof(ALfloat));
if(!temp)
{
AL_PRINT("Failed reallocation!");
abort();
}
for(i = 0;i < state->BufferLength;i++)
state->SampleBuffer[i] = 0.0f;
}
state->Tap[0].delay = (ALuint)(Effect->Echo.Delay * Context->Frequency);
state->Tap[1].delay = (ALuint)(Effect->Echo.LRDelay * Context->Frequency);
state->Tap[1].delay += state->Tap[0].delay;
lrpan = Effect->Echo.Spread*0.5f + 0.5f;
state->GainL = aluSqrt( lrpan);
@ -111,32 +132,30 @@ ALvoid EchoUpdate(ALeffectState *effect, ALCcontext *Context, ALeffect *Effect)
ALvoid EchoProcess(ALeffectState *effect, const ALeffectslot *Slot, ALuint SamplesToDo, const ALfloat *SamplesIn, ALfloat (*SamplesOut)[OUTPUTCHANNELS])
{
ALechoState *state = (ALechoState*)effect;
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 gain = Slot->Gain;
ALfloat samp[2];
const ALuint mask = state->BufferLength-1;
const ALuint tap1 = state->Tap[0].delay;
const ALuint tap2 = state->Tap[1].delay;
ALuint offset = state->Offset;
const ALfloat gain = Slot->Gain;
ALfloat samp[2], smp;
ALuint i;
for(i = 0;i < SamplesToDo;i++)
for(i = 0;i < SamplesToDo;i++,offset++)
{
// Apply damping
samp[0] = lpFilter2P(&state->iirFilter, 0, state->SampleBuffer[tap2off]+SamplesIn[i]);
// 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;
smp = state->SampleBuffer[(offset-tap1) & mask];
samp[0] = smp * state->GainL;
samp[1] = smp * state->GainR;
// Sample second tap. Reverse LR panning
samp[0] += state->SampleBuffer[tap2off]*state->GainR;
samp[1] += state->SampleBuffer[tap2off]*state->GainL;
smp = state->SampleBuffer[(offset-tap2) & mask];
samp[0] += smp * state->GainR;
samp[1] += smp * state->GainL;
// Apply damping and feedback gain to the second tap, and mix in the
// new sample
smp = lpFilter2P(&state->iirFilter, 0, smp+SamplesIn[i]);
state->SampleBuffer[offset&mask] = smp * state->FeedGain;
// Apply slot gain
samp[0] *= gain;
samp[1] *= gain;
@ -148,13 +167,10 @@ ALvoid EchoProcess(ALeffectState *effect, const ALeffectslot *Slot, ALuint Sampl
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;
state->Offset = offset;
}
ALeffectState *EchoCreate(ALCcontext *Context)
ALeffectState *EchoCreate(void)
{
ALechoState *state;
ALuint i, maxlen;
@ -170,8 +186,8 @@ ALeffectState *EchoCreate(ALCcontext *Context)
state->state.Update = EchoUpdate;
state->state.Process = EchoProcess;
maxlen = (ALuint)(AL_ECHO_MAX_DELAY * Context->Frequency);
maxlen += (ALuint)(AL_ECHO_MAX_LRDELAY * Context->Frequency);
maxlen = (ALuint)(AL_ECHO_MAX_DELAY * MAX_ECHO_FREQ);
maxlen += (ALuint)(AL_ECHO_MAX_LRDELAY * MAX_ECHO_FREQ);
// Use the next power of 2 for the buffer length, so the tap offsets can be
// wrapped using a mask instead of a modulo
@ -187,9 +203,9 @@ ALeffectState *EchoCreate(ALCcontext *Context)
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->Tap[0].delay = 0;
state->Tap[1].delay = 0;
state->Offset = 0;
state->GainL = 0.0f;
state->GainR = 0.0f;

View File

@ -31,6 +31,10 @@
#include "alError.h"
#include "alu.h"
// Just a soft maximum. Being higher will cause VerbUpdate to reallocate the
// sample buffer which may cause an abort if realloc fails
#define MAX_REVERB_FREQ 192000
typedef struct DelayLine
{
// The delay lines use sample lengths that are powers of 2 to allow
@ -46,6 +50,7 @@ typedef struct ALverbState {
// All delay lines are allocated as a single buffer to reduce memory
// fragmentation and management code.
ALfloat *SampleBuffer;
ALuint TotalLength;
// Master effect low-pass filter (2 chained 1-pole filters).
FILTER LpFilter;
ALfloat LpHistory[2];
@ -143,6 +148,46 @@ static ALuint NextPowerOf2(ALuint value)
return powerOf2;
}
static ALuint CalcLengths(ALuint length[13], ALuint frequency)
{
ALuint samples, totalLength, index;
// All line lengths are powers of 2, calculated from their lengths, with
// an additional sample in case of rounding errors.
// See VerbUpdate() for an explanation of the additional calculation
// added to the master line length.
samples = (ALuint)
((MASTER_LINE_LENGTH +
(LATE_LINE_LENGTH[0] * (1.0f + LATE_LINE_MULTIPLIER) *
(DECO_FRACTION * ((DECO_MULTIPLIER * DECO_MULTIPLIER *
DECO_MULTIPLIER) - 1.0f)))) *
frequency) + 1;
length[0] = NextPowerOf2(samples);
totalLength = length[0];
for(index = 0;index < 4;index++)
{
samples = (ALuint)(EARLY_LINE_LENGTH[index] * frequency) + 1;
length[1 + index] = NextPowerOf2(samples);
totalLength += length[1 + index];
}
for(index = 0;index < 4;index++)
{
samples = (ALuint)(ALLPASS_LINE_LENGTH[index] * frequency) + 1;
length[5 + index] = NextPowerOf2(samples);
totalLength += length[5 + index];
}
for(index = 0;index < 4;index++)
{
samples = (ALuint)(LATE_LINE_LENGTH[index] *
(1.0f + LATE_LINE_MULTIPLIER) * frequency) + 1;
length[9 + index] = NextPowerOf2(samples);
totalLength += length[9 + index];
}
return totalLength;
}
// Basic delay line input/output routines.
static __inline ALfloat DelayLineOut(DelayLine *Delay, ALuint offset)
{
@ -369,12 +414,62 @@ static __inline ALint aluCart2LUTpos(ALfloat re, ALfloat im)
// This updates the reverb state. This is called any time the reverb effect
// is loaded into a slot.
ALvoid VerbUpdate(ALeffectState *effect, ALCcontext *Context, ALeffect *Effect)
ALvoid VerbUpdate(ALeffectState *effect, ALCcontext *Context, const ALeffect *Effect)
{
ALverbState *State = (ALverbState*)effect;
ALuint index;
ALfloat length, mixCoeff, cw, g, coeff;
ALfloat hfRatio = Effect->Reverb.DecayHFRatio;
ALuint lengths[13], totalLength;
totalLength = CalcLengths(lengths, Context->Frequency);
if(totalLength > State->TotalLength)
{
void *temp;
State->TotalLength = totalLength;
temp = realloc(State->SampleBuffer, State->TotalLength * sizeof(ALfloat));
if(!temp)
{
AL_PRINT("Failed reallocation!");
abort();
}
State->SampleBuffer = temp;
for(index = 0; index < totalLength;index++)
State->SampleBuffer[index] = 0.0f;
// All lines share a single sample buffer
State->Delay.Mask = lengths[0] - 1;
State->Delay.Line = &State->SampleBuffer[0];
totalLength = lengths[0];
for(index = 0;index < 4;index++)
{
State->Early.Delay[index].Mask = lengths[1 + index] - 1;
State->Early.Delay[index].Line = &State->SampleBuffer[totalLength];
totalLength += lengths[1 + index];
}
for(index = 0;index < 4;index++)
{
State->Late.ApDelay[index].Mask = lengths[5 + index] - 1;
State->Late.ApDelay[index].Line = &State->SampleBuffer[totalLength];
totalLength += lengths[5 + index];
}
for(index = 0;index < 4;index++)
{
State->Late.Delay[index].Mask = lengths[9 + index] - 1;
State->Late.Delay[index].Line = &State->SampleBuffer[totalLength];
totalLength += lengths[9 + index];
}
}
for(index = 0;index < 4;index++)
{
State->Early.Offset[index] = (ALuint)(EARLY_LINE_LENGTH[index] *
Context->Frequency);
State->Late.ApOffset[index] = (ALuint)(ALLPASS_LINE_LENGTH[index] *
Context->Frequency);
}
// Calculate the master low-pass filter (from the master effect HF gain).
cw = cos(2.0 * M_PI * Effect->Reverb.HFReference / Context->Frequency);
@ -659,10 +754,10 @@ ALvoid EAXVerbProcess(ALeffectState *effect, const ALeffectslot *Slot, ALuint Sa
// This creates the reverb state. It should be called only when the reverb
// effect is loaded into a slot that doesn't already have a reverb effect.
ALeffectState *VerbCreate(ALCcontext *Context)
ALeffectState *VerbCreate(void)
{
ALverbState *State = NULL;
ALuint samples, length[13], totalLength, index;
ALuint length[13], totalLength, index;
State = malloc(sizeof(ALverbState));
if(!State)
@ -675,42 +770,10 @@ ALeffectState *VerbCreate(ALCcontext *Context)
State->state.Update = VerbUpdate;
State->state.Process = VerbProcess;
// All line lengths are powers of 2, calculated from their lengths, with
// an additional sample in case of rounding errors.
totalLength = CalcLengths(length, MAX_REVERB_FREQ);
// See VerbUpdate() for an explanation of the additional calculation
// added to the master line length.
samples = (ALuint)
((MASTER_LINE_LENGTH +
(LATE_LINE_LENGTH[0] * (1.0f + LATE_LINE_MULTIPLIER) *
(DECO_FRACTION * ((DECO_MULTIPLIER * DECO_MULTIPLIER *
DECO_MULTIPLIER) - 1.0f)))) *
Context->Frequency) + 1;
length[0] = NextPowerOf2(samples);
totalLength = length[0];
for(index = 0;index < 4;index++)
{
samples = (ALuint)(EARLY_LINE_LENGTH[index] * Context->Frequency) + 1;
length[1 + index] = NextPowerOf2(samples);
totalLength += length[1 + index];
}
for(index = 0;index < 4;index++)
{
samples = (ALuint)(ALLPASS_LINE_LENGTH[index] * Context->Frequency) + 1;
length[5 + index] = NextPowerOf2(samples);
totalLength += length[5 + index];
}
for(index = 0;index < 4;index++)
{
samples = (ALuint)(LATE_LINE_LENGTH[index] *
(1.0f + LATE_LINE_MULTIPLIER) * Context->Frequency) + 1;
length[9 + index] = NextPowerOf2(samples);
totalLength += length[9 + index];
}
// All lines share a single sample buffer and have their masks and start
// addresses calculated once.
State->SampleBuffer = malloc(totalLength * sizeof(ALfloat));
State->TotalLength = totalLength;
State->SampleBuffer = malloc(State->TotalLength * sizeof(ALfloat));
if(!State->SampleBuffer)
{
free(State);
@ -741,9 +804,7 @@ ALeffectState *VerbCreate(ALCcontext *Context)
State->Early.Delay[index].Line = &State->SampleBuffer[totalLength];
totalLength += length[1 + index];
// The early delay lines have their read offsets calculated once.
State->Early.Offset[index] = (ALuint)(EARLY_LINE_LENGTH[index] *
Context->Frequency);
State->Early.Offset[index] = 0;
}
State->Late.Gain = 0.0f;
@ -758,9 +819,7 @@ ALeffectState *VerbCreate(ALCcontext *Context)
State->Late.ApDelay[index].Line = &State->SampleBuffer[totalLength];
totalLength += length[5 + index];
// The late all-pass lines have their read offsets calculated once.
State->Late.ApOffset[index] = (ALuint)(ALLPASS_LINE_LENGTH[index] *
Context->Frequency);
State->Late.ApOffset[index] = 0;
}
for(index = 0;index < 4;index++)
@ -787,9 +846,9 @@ ALeffectState *VerbCreate(ALCcontext *Context)
return &State->state;
}
ALeffectState *EAXVerbCreate(ALCcontext *Context)
ALeffectState *EAXVerbCreate(void)
{
ALeffectState *State = VerbCreate(Context);
ALeffectState *State = VerbCreate();
if(State) State->Process = EAXVerbProcess;
return State;
}

View File

@ -55,13 +55,13 @@ ALvoid ReleaseALAuxiliaryEffectSlots(ALCcontext *Context);
struct ALeffectState {
ALvoid (*Destroy)(ALeffectState *State);
ALvoid (*Update)(ALeffectState *State, ALCcontext *Context, ALeffect *Effect);
ALvoid (*Update)(ALeffectState *State, ALCcontext *Context, const ALeffect *Effect);
ALvoid (*Process)(ALeffectState *State, const ALeffectslot *Slot, ALuint SamplesToDo, const ALfloat *SamplesIn, ALfloat (*SamplesOut)[OUTPUTCHANNELS]);
};
ALeffectState *EAXVerbCreate(ALCcontext *Context);
ALeffectState *VerbCreate(ALCcontext *Context);
ALeffectState *EchoCreate(ALCcontext *Context);
ALeffectState *EAXVerbCreate(void);
ALeffectState *VerbCreate(void);
ALeffectState *EchoCreate(void);
#define ALEffect_Destroy(a) ((a)->Destroy((a)))
#define ALEffect_Update(a,b,c) ((a)->Update((a),(b),(c)))

View File

@ -422,11 +422,11 @@ static ALvoid InitializeEffect(ALCcontext *Context, ALeffectslot *ALEffectSlot,
if(effect)
{
if(effect->type == AL_EFFECT_EAXREVERB)
NewState = EAXVerbCreate(Context);
NewState = EAXVerbCreate();
else if(effect->type == AL_EFFECT_REVERB)
NewState = VerbCreate(Context);
NewState = VerbCreate();
else if(effect->type == AL_EFFECT_ECHO)
NewState = EchoCreate(Context);
NewState = EchoCreate();
/* No new state? An error occured.. */
if(!NewState)
return;