From c0ccd31a3e5294ffa6f138ff755177cd9bad6a4b Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sun, 16 Nov 2008 00:29:49 -0800 Subject: [PATCH] Implement a new reverb effect Code created and graciously provided by Christopher Fitzgerald --- Alc/ALu.c | 72 +---- Alc/alcReverb.c | 549 +++++++++++++++++++++++++++++++++++++ CMakeLists.txt | 1 + OpenAL32/Include/alAuxEffectSlot.h | 11 +- OpenAL32/Include/alReverb.h | 26 ++ OpenAL32/alAuxEffectSlot.c | 51 +--- 6 files changed, 600 insertions(+), 110 deletions(-) create mode 100644 Alc/alcReverb.c create mode 100644 OpenAL32/Include/alReverb.h diff --git a/Alc/ALu.c b/Alc/ALu.c index fbf5a128..1f755621 100644 --- a/Alc/ALu.c +++ b/Alc/ALu.c @@ -33,6 +33,7 @@ #include "alAuxEffectSlot.h" #include "alu.h" #include "bs2b.h" +#include "alReverb.h" #if defined (HAVE_FLOAT_H) #include @@ -459,28 +460,24 @@ static ALvoid CalcSourceParams(ALCcontext *ALContext, ALsource *ALSource, if(ALSource->Send[0].Slot && ALSource->Send[0].Slot->effect.type != AL_EFFECT_NULL) { - // If the slot's auxilliary send auto is off, the data sent to the - // effect slot is the same as the dry path, sans filter effects - if(!ALSource->Send[0].Slot->AuxSendAuto) + if(ALSource->Send[0].Slot->AuxSendAuto) { + // Apply minimal attenuation in place of missing statistical + // reverb model. + WetMix *= pow(DryMix, 1.0f / 2.0f); + } + else + { + // If the slot's auxilliary send auto is off, the data sent to the + // effect slot is the same as the dry path, sans filter effects WetMix = DryMix; WetGainHF = DryGainHF; } - // Note that these are really applied by the effect slot. However, - // it's easier to handle them here (particularly the lowpass - // filter). Applying the gain to the individual sources going to - // the effect slot should have the same effect as applying the gain - // to the accumulated sources in the effect slot. - // vol1*g + vol2*g + ... voln*g = (vol1+vol2+...voln)*g - WetMix *= ALSource->Send[0].Slot->Gain; + // Note that this is really applied by the effect slot. However, + // it's easier (more optimal) to handle it here. if(ALSource->Send[0].Slot->effect.type == AL_EFFECT_REVERB) - { - WetMix *= ALSource->Send[0].Slot->effect.Reverb.Gain; WetGainHF *= ALSource->Send[0].Slot->effect.Reverb.GainHF; - WetGainHF *= pow(ALSource->Send[0].Slot->effect.Reverb.AirAbsorptionGainHF, - Distance * MetersPerUnit); - } } else { @@ -969,50 +966,7 @@ ALvoid aluMixData(ALCcontext *ALContext,ALvoid *buffer,ALsizei size,ALenum forma while(ALEffectSlot) { if(ALEffectSlot->effect.type == AL_EFFECT_REVERB) - { - ALfloat *DelayBuffer = ALEffectSlot->ReverbBuffer; - ALuint Pos = ALEffectSlot->ReverbPos; - ALuint LatePos = ALEffectSlot->ReverbLatePos; - ALuint ReflectPos = ALEffectSlot->ReverbReflectPos; - ALuint Length = ALEffectSlot->ReverbLength; - ALfloat DecayGain = ALEffectSlot->ReverbDecayGain; - ALfloat DecayHFRatio = ALEffectSlot->effect.Reverb.DecayHFRatio; - ALfloat ReflectGain = ALEffectSlot->effect.Reverb.ReflectionsGain; - ALfloat LateReverbGain = ALEffectSlot->effect.Reverb.LateReverbGain; - ALfloat sample, lowsample; - - WetFilter = &ALEffectSlot->iirFilter; - for(i = 0;i < SamplesToDo;i++) - { - DelayBuffer[Pos] = WetBuffer[i]; - - sample = DelayBuffer[ReflectPos] * ReflectGain; - - DelayBuffer[LatePos] *= LateReverbGain; - - Pos = (Pos+1) % Length; - lowsample = lpFilter(WetFilter, DelayBuffer[Pos]); - lowsample += (DelayBuffer[Pos]-lowsample) * DecayHFRatio; - - DelayBuffer[LatePos] += lowsample * DecayGain; - - sample += DelayBuffer[LatePos]; - - DryBuffer[i][FRONT_LEFT] += sample; - DryBuffer[i][FRONT_RIGHT] += sample; - DryBuffer[i][SIDE_LEFT] += sample; - DryBuffer[i][SIDE_RIGHT] += sample; - DryBuffer[i][BACK_LEFT] += sample; - DryBuffer[i][BACK_RIGHT] += sample; - - LatePos = (LatePos+1) % Length; - ReflectPos = (ReflectPos+1) % Length; - } - - ALEffectSlot->ReverbPos = Pos; - ALEffectSlot->ReverbLatePos = LatePos; - ALEffectSlot->ReverbReflectPos = ReflectPos; - } + VerbProcess(ALEffectSlot->ReverbState, SamplesToDo, WetBuffer, DryBuffer); ALEffectSlot = ALEffectSlot->next; } diff --git a/Alc/alcReverb.c b/Alc/alcReverb.c new file mode 100644 index 00000000..badbac34 --- /dev/null +++ b/Alc/alcReverb.c @@ -0,0 +1,549 @@ +// TODO: Look into the distance attenuation done by the statistical model, +// and see if it matches 1 / sqrt (distance) or some variant thereof. +// If not, try to map it so it can replace the current dry-path model. +// Also see how it responds to the distance model. Then if necessary +// update ALu.c to compensate. +// TODO: Finalize all updates and add necessary comments. Merge changes +// with latest GIT snapshot and test all parameters thoroughly. When +// ready produce some .diff files for the changes and include with +// alReverb.c for presentation. + +#include "config.h" + +#include +#include + +#include "AL/al.h" +#include "AL/alc.h" +#include "alMain.h" +#include "alAuxEffectSlot.h" +#include "alEffect.h" +#include "alReverb.h" + +#ifdef HAVE_SQRTF +#define aluSqrt(x) ((ALfloat)sqrtf((float)(x))) +#else +#define aluSqrt(x) ((ALfloat)sqrt((double)(x))) +#endif + +// fixes for mingw32. +#if defined(max) && !defined(__max) +#define __max max +#endif +#if defined(min) && !defined(__min) +#define __min min +#endif + +typedef struct DelayLine +{ + // The delay lines use lengths that are powers of 2 to allow bitmasking + // instead of modulus wrapping. + ALuint Mask; + ALfloat *Line; +} DelayLine; + +struct ALverbState +{ + // All delay lines are allocated as a single buffer to reduce memory + // fragmentation and teardown code. + ALfloat *SampleBuffer; + // Master reverb gain. + ALfloat Gain; + // Initial reverb delay. + DelayLine Delay; + // The tap points for the initial delay. First tap goes to early + // reflections, the second to late reverb. + ALuint Tap[2]; + struct { + // Gain for early reflections. + ALfloat Gain; + // Early reflections are done with 4 delay lines. + ALfloat Coeff[4]; + DelayLine Delay[4]; + ALuint Offset[4]; + } Early; + struct { + // Gain for late reverb. + ALfloat Gain; + // Diffusion of late reverb. + ALfloat Diffusion; + // Late reverb is done with 8 delay lines. + ALfloat Coeff[8]; + DelayLine Delay[8]; + ALuint Offset[8]; + // The input and last 4 delay lines are low-pass filtered. + ALfloat LpCoeff[5]; + ALfloat LpSample[5]; + } Late; + ALuint Offset; +}; + +// All delay line lengths are specified in seconds. + +// The length of the initial delay line (a sum of the maximum delay before +// early reflections and late reverb; 0.3 + 0.1). +static const ALfloat MASTER_LINE_LENGTH = 0.4000f; + +// The lengths of the early delay lines. +static const ALfloat EARLY_LINE_LENGTH[4] = +{ + 0.0015f, 0.0045f, 0.0135f, 0.0405f +}; + +// The lengths of the late delay lines. +static const ALfloat LATE_LINE_LENGTH[8] = +{ + 0.0015f, 0.0037f, 0.0093f, 0.0234f, + 0.0100f, 0.0150f, 0.0225f, 0.0337f +}; + +// The last 4 late delay lines have a variable length dependent on the effect +// density parameter and this multiplier. +static const ALfloat LATE_LINE_MULTIPLIER = 9.0f; + +static ALuint NextPowerOf2(ALuint value) +{ + ALuint powerOf2 = 1; + + if(value) + { + value--; + while(value) + { + value >>= 1; + powerOf2 <<= 1; + } + } + return powerOf2; +} + +// Basic delay line input/output routines. +static __inline ALfloat DelayLineOut(DelayLine *Delay, ALuint offset) +{ + return Delay->Line[offset&Delay->Mask]; +} + +static __inline ALvoid DelayLineIn(DelayLine *Delay, ALuint offset, ALfloat in) +{ + Delay->Line[offset&Delay->Mask] = in; +} + +// Delay line output routine for early reflections. +static __inline ALfloat EarlyDelayLineOut(ALverbState *State, ALuint index) +{ + return State->Early.Coeff[index] * + DelayLineOut(&State->Early.Delay[index], + State->Offset - State->Early.Offset[index]); +} + +// Given an input sample, this function produces a decorrelated stereo output +// for early reflections. +static __inline ALvoid EarlyReflection(ALverbState *State, ALfloat in, ALfloat *out) +{ + ALfloat d[4], v, f[4]; + + // Obtain the decayed results of each early delay line. + d[0] = EarlyDelayLineOut(State, 0); + d[1] = EarlyDelayLineOut(State, 1); + d[2] = EarlyDelayLineOut(State, 2); + d[3] = EarlyDelayLineOut(State, 3); + + /* The following uses a lossless scattering junction from waveguide + * theory. It actually amounts to a householder mixing matrix, which + * will produce a maximally diffuse response, and means this can probably + * be considered a simple FDN. + * N + * --- + * \ + * v = 2/N / di + * --- + * i=1 + */ + v = (d[0] + d[1] + d[2] + d[3]) * 0.5f; + // The junction is loaded with the input here. + v += in; + + // Calculate the feed values for the delay lines. + f[0] = v - d[0]; + f[1] = v - d[1]; + f[2] = v - d[2]; + f[3] = v - d[3]; + + // To increase reflection complexity (and help reduce coloration) the + // delay lines cyclicly refeed themselves (0 -> 1 -> 3 -> 2 -> 0...). + DelayLineIn(&State->Early.Delay[0], State->Offset, f[2]); + DelayLineIn(&State->Early.Delay[1], State->Offset, f[0]); + DelayLineIn(&State->Early.Delay[2], State->Offset, f[3]); + DelayLineIn(&State->Early.Delay[3], State->Offset, f[1]); + + // To decorrelate the output for stereo separation, the cyclical nature + // of the feed path is exploited. The two outputs are obtained from the + // inner delay lines. + // Output is instant by using the inputs to them instead of taking the + // result of the two delay lines directly (f[0] and f[3] instead of d[1] + // and d[2]). + out[0] = State->Early.Gain * f[0]; + out[1] = State->Early.Gain * f[3]; +} + +// Delay line output routine for late reverb. +static __inline ALfloat LateDelayLineOut(ALverbState *State, ALuint index) +{ + return State->Late.Coeff[index] * + DelayLineOut(&State->Late.Delay[index], + State->Offset - State->Late.Offset[index]); +} + +// Low-pass filter input/output routine for late reverb. +static __inline ALfloat LateLowPassInOut(ALverbState *State, ALuint index, ALfloat in) +{ + State->Late.LpSample[index] = in + ((State->Late.LpSample[index] - in) * + State->Late.LpCoeff[index]); + return State->Late.LpSample[index]; +} + +// Given an input sample, this function produces a decorrelated stereo output +// for late reverb. +static __inline ALvoid LateReverb(ALverbState *State, ALfloat in, ALfloat *out) +{ + ALfloat din, d[8], v, dv, f[8]; + + // Since the input will be sent directly to the output as in the early + // reflections function, it needs to take into account some immediate + // absorption. + in = LateLowPassInOut(State, 0, in); + + // When diffusion is full, no input is directly passed to the variable- + // length delay lines (the last 4). + din = (1.0f - State->Late.Diffusion) * in; + + // Obtain the decayed results of the fixed-length delay lines. + d[0] = LateDelayLineOut(State, 0); + d[1] = LateDelayLineOut(State, 1); + d[2] = LateDelayLineOut(State, 2); + d[3] = LateDelayLineOut(State, 3); + // Obtain the decayed and low-pass filtered results of the variable- + // length delay lines. + d[4] = LateLowPassInOut(State, 1, LateDelayLineOut(State, 4)); + d[5] = LateLowPassInOut(State, 2, LateDelayLineOut(State, 5)); + d[6] = LateLowPassInOut(State, 3, LateDelayLineOut(State, 6)); + d[7] = LateLowPassInOut(State, 4, LateDelayLineOut(State, 7)); + + // The waveguide formula used in the early reflections function works + // great for high diffusion, but it is not obviously paramerized to allow + // a variable diffusion. With only limited time and resources, what + // follows is the best variation of that formula I could come up with. + // First, there are 8 delay lines used. The first 4 are fixed-length and + // generate the highest density of the diffuse response. The last 4 are + // variable-length, and are used to smooth out the diffuse response. The + // density effect parameter alters their length. The inner two delay + // lines of each group have their signs reversed (more about this later). + v = (d[0] - d[1] - d[2] + d[3] + + d[4] - d[5] - d[6] + d[7]) * 0.25f; + // Diffusion is applied as a reduction of the junction pressure for all + // branches. This presents two problems. When the diffusion factor (0 + // to 1) reaches 0.5, the average feed value is reduced (the junction + // becomes lossy). Thus, at 0.5 the signal decays almost twice as fast + // as it should. The second problem is the introduction of some + // resonant frequencies (coloration). The reversed signs above are used + // to help combat some of the coloration by adding variations along the + // feed cycle. + v *= State->Late.Diffusion; + // Load the junction with the input. To reduce the noticeable echo of + // the longer delay lines (the variable-length ones) the input is loaded + // with the inverse of the effect diffusion. So at full diffusion, the + // input is not applied to the last 4 delay lines. Input signs reversed + // to balance the equation. + dv = v + din; + v += in; + + // As with the reversed signs above, to balance the equation the signs + // need to be reversed here, too. + f[0] = d[0] - v; + f[1] = d[1] + v; + f[2] = d[2] + v; + f[3] = d[3] - v; + f[4] = d[4] - dv; + f[5] = d[5] + dv; + f[6] = d[6] + dv; + f[7] = d[7] - dv; + + // Feed the fixed-length delay lines with their own cycle (0 -> 1 -> 3 -> + // 2 -> 0...). + DelayLineIn(&State->Late.Delay[0], State->Offset, f[2]); + DelayLineIn(&State->Late.Delay[1], State->Offset, f[0]); + DelayLineIn(&State->Late.Delay[2], State->Offset, f[3]); + DelayLineIn(&State->Late.Delay[3], State->Offset, f[1]); + // Feed the variable-length delay lines with their cycle (4 -> 6 -> 7 -> + // 5 -> 4...). + DelayLineIn(&State->Late.Delay[4], State->Offset, f[5]); + DelayLineIn(&State->Late.Delay[5], State->Offset, f[7]); + DelayLineIn(&State->Late.Delay[6], State->Offset, f[4]); + DelayLineIn(&State->Late.Delay[7], State->Offset, f[6]); + + // Output is derived from the values fed to the inner two variable-length + // delay lines (5 and 6). + out[0] = State->Late.Gain * f[7]; + out[1] = State->Late.Gain * f[4]; +} + +// 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. +ALverbState *VerbCreate(ALCcontext *Context) +{ + ALverbState *State = NULL; + ALuint length[13], totalLength, index; + + State = malloc(sizeof(ALverbState)); + if(!State) + return NULL; + + // All line lengths are powers of 2, calculated from the line timings and + // the addition of an extra sample (for safety). + length[0] = NextPowerOf2((ALuint)(MASTER_LINE_LENGTH*Context->Frequency) + 1); + totalLength = length[0]; + for(index = 0;index < 4;index++) + { + length[1+index] = NextPowerOf2((ALuint)(EARLY_LINE_LENGTH[index]*Context->Frequency) + 1); + totalLength += length[1+index]; + } + for(index = 0;index < 4;index++) + { + length[5+index] = NextPowerOf2((ALuint)(LATE_LINE_LENGTH[index]*Context->Frequency) + 1); + totalLength += length[5+index]; + } + for(index = 4;index < 8;index++) + { + length[5+index] = NextPowerOf2((ALuint)(LATE_LINE_LENGTH[index]*(1.0f + LATE_LINE_MULTIPLIER)*Context->Frequency) + 1); + totalLength += length[5+index]; + } + + // They all share a single sample buffer. + State->SampleBuffer = malloc(totalLength * sizeof(ALfloat)); + if(!State->SampleBuffer) + { + free(State); + return NULL; + } + for(index = 0; index < totalLength;index++) + State->SampleBuffer[index] = 0.0f; + + // Each one has its mask and start address calculated one time. + State->Gain = 0.0f; + State->Delay.Mask = length[0] - 1; + State->Delay.Line = &State->SampleBuffer[0]; + totalLength = length[0]; + + State->Tap[0] = 0; + State->Tap[1] = 0; + + State->Early.Gain = 0.0f; + // All fixed-length delay lines have their read-write offsets calculated + // one time. + for(index = 0;index < 4;index++) + { + State->Early.Coeff[index] = 0.0f; + State->Early.Delay[index].Mask = length[1 + index] - 1; + State->Early.Delay[index].Line = &State->SampleBuffer[totalLength]; + totalLength += length[1 + index]; + + State->Early.Offset[index] = (ALuint)(EARLY_LINE_LENGTH[index] * Context->Frequency); + } + + State->Late.Gain = 0.0f; + State->Late.Diffusion = 0.0f; + for(index = 0;index < 8;index++) + { + State->Late.Coeff[index] = 0.0f; + State->Late.Delay[index].Mask = length[5 + index] - 1; + State->Late.Delay[index].Line = &State->SampleBuffer[totalLength]; + totalLength += length[5 + index]; + + State->Late.Offset[index] = 0; + if(index < 4) + { + State->Late.Offset[index] = (ALuint)(LATE_LINE_LENGTH[index] * Context->Frequency); + State->Late.LpCoeff[index] = 0.0f; + State->Late.LpSample[index] = 0.0f; + } + else if(index == 4) + { + State->Late.LpCoeff[index] = 0.0f; + State->Late.LpSample[index] = 0.0f; + } + } + + State->Offset = 0; + return State; +} + +// This destroys the reverb state. It should be called only when the effect +// slot has a different (or no) effect loaded over the reverb effect. +ALvoid VerbDestroy(ALverbState *State) +{ + if(State) + { + free(State->SampleBuffer); + State->SampleBuffer = NULL; + free(State); + } +} + +// This updates the reverb state. This is called any time the reverb effect +// is loaded into a slot. +ALvoid VerbUpdate(ALCcontext *Context, ALeffectslot *Slot, ALeffect *Effect) +{ + ALverbState *State = Slot->ReverbState; + ALuint index, index2; + ALfloat length, lpcoeff, cw, g; + ALfloat hfRatio = Effect->Reverb.DecayHFRatio; + + // Calculate the master gain (from the slot and master reverb gain). + State->Gain = Slot->Gain * Effect->Reverb.Gain; + + // Calculate the initial delay taps. + length = Effect->Reverb.ReflectionsDelay; + State->Tap[0] = (ALuint)(length * Context->Frequency); + length += Effect->Reverb.LateReverbDelay; + State->Tap[1] = (ALuint)(length * Context->Frequency); + + // Calculate the early reflections gain. Right now this uses a gain of + // 0.75 to compensate for the increase in density. It should probably + // use a power (RMS) based measurement from the resulting distribution of + // early delay lines. + State->Early.Gain = Effect->Reverb.ReflectionsGain * 0.75f; + + // Calculate the gain (coefficient) for each early delay line. + for(index = 0;index < 4;index++) + State->Early.Coeff[index] = pow(10.0f, EARLY_LINE_LENGTH[index] / + Effect->Reverb.LateReverbDelay * + -60.0f / 20.0f); + + // Calculate the late reverb gain, adjusted by density, diffusion, and + // decay time. To be accurate, the adjustments should probably use power + // measurements for each contribution, but they are not too bad as they + // are. + State->Late.Gain = Effect->Reverb.LateReverbGain * + (0.45f + (0.55f * Effect->Reverb.Density)) * + (1.0f - (0.25f * Effect->Reverb.Diffusion)) * + (1.0f - (0.025f * Effect->Reverb.DecayTime)); + State->Late.Diffusion = Effect->Reverb.Diffusion; + + // The EFX specification does not make it clear whether the air + // absorption parameter should always take effect. Both Generic Software + // and Generic Hardware only apply it when HF limit is flagged, so that's + // what is done here. + // If the HF limit parameter is flagged, calculate an appropriate limit + // based on the air absorption parameter. + if(Effect->Reverb.DecayHFLimit) + { + ALfloat limitRatio; + + // The following is my best guess at how to limit the HF ratio by the + // air absorption parameter. + // For each of the last 4 delays, find the attenuation due to air + // absorption in dB (converting delay time to meters using the speed + // of sound). Then reversing the decay equation, solve for HF ratio. + // The delay length is cancelled out of the equation, so it can be + // calculated once for all lines. + limitRatio = 1.0f / (log10(Effect->Reverb.AirAbsorptionGainHF) * + SPEEDOFSOUNDMETRESPERSEC*Effect->Reverb.DecayTime/ + -60.0f * 20.0f); + // Need to limit the result to a minimum of 0.1, just like the HF + // ratio parameter. + limitRatio = __max(limitRatio, 0.1f); + + // Using the limit calculated above, apply the upper bound to the + // HF ratio. + hfRatio = __min(hfRatio, limitRatio); + } + + cw = cos(2.0f*3.141592654f * LOWPASSFREQCUTOFF / Context->Frequency); + + for(index = 0;index < 8;index++) + { + // Calculate the length (in seconds) of each delay line. + length = LATE_LINE_LENGTH[index]; + if(index >= 4) + { + index2 = index - 3; + + length *= 1.0f + (Effect->Reverb.Density * LATE_LINE_MULTIPLIER); + + // Calculate the delay offset for the variable-length delay + // lines. + State->Late.Offset[index] = (ALuint)(length * Context->Frequency); + + // Calculate the decay equation for each low-pass filter. + g = pow(10.0f, length / (Effect->Reverb.DecayTime * hfRatio) * + -60.0f / 20.0f); + g = __max(g, 0.1f); + g *= g; + // Calculate the gain (coefficient) for each low-pass filter. + lpcoeff = 0.0f; + if(g < 0.9999f) // 1-epsilon + lpcoeff = (1 - g*cw - aluSqrt(2*g*(1-cw) - g*g*(1 - cw*cw))) / (1 - g); + + // Very low decay times will produce minimal output, so apply an + // upper bound to the coefficient. + State->Late.LpCoeff[index2] = __min(lpcoeff, 0.98f); + } + + // Calculate the gain (coefficient) for each line. + State->Late.Coeff[index] = pow(10.0f, length / Effect->Reverb.DecayTime * + -60.0f / 20.0f); + } + + // This just calculates the coefficient for the late reverb input low- + // pass filter. It is calculated based the average (hence -30 instead + // of -60) length of the inner two variable-length delay lines. + length = LATE_LINE_LENGTH[5] * (1.0f + Effect->Reverb.Density * LATE_LINE_MULTIPLIER) + + LATE_LINE_LENGTH[6] * (1.0f + Effect->Reverb.Density * LATE_LINE_MULTIPLIER); + + g = pow(10.0f, length / (Effect->Reverb.DecayTime * hfRatio) * -30.0f / 20.0f); + g = __max(g, 0.1f); + g *= g; + + lpcoeff = 0.0f; + if(g < 0.9999f) // 1-epsilon + lpcoeff = (1 - g*cw - aluSqrt(2*g*(1-cw) - g*g*(1 - cw*cw))) / (1 - g); + + State->Late.LpCoeff[0] = __min(lpcoeff, 0.98f); +} + +// This processes the reverb state, given the input samples and an output +// buffer. +ALvoid VerbProcess(ALverbState *State, ALuint SamplesToDo, const ALfloat *SamplesIn, ALfloat (*SamplesOut)[OUTPUTCHANNELS]) +{ + ALuint index; + ALfloat in, early[2], late[2], out[2]; + + for(index = 0;index < SamplesToDo;index++) + { + // Feed the initial delay line. + DelayLineIn(&State->Delay, State->Offset, SamplesIn[index]); + + // Calculate the early reflection from the first delay tap. + in = DelayLineOut(&State->Delay, State->Offset - State->Tap[0]); + EarlyReflection(State, in, early); + + // Calculate the late reverb from the second delay tap. + in = DelayLineOut(&State->Delay, State->Offset - State->Tap[1]); + LateReverb(State, in, late); + + // Mix early reflections and late reverb. + out[0] = State->Gain * (early[0] + late[0]); + out[1] = State->Gain * (early[1] + late[1]); + + // Step all delays forward one sample. + State->Offset++; + + // Output the results. + SamplesOut[index][FRONT_LEFT] += out[0]; + SamplesOut[index][FRONT_RIGHT] += out[1]; + SamplesOut[index][SIDE_LEFT] += out[0]; + SamplesOut[index][SIDE_RIGHT] += out[1]; + SamplesOut[index][BACK_LEFT] += out[0]; + SamplesOut[index][BACK_RIGHT] += out[1]; + } +} diff --git a/CMakeLists.txt b/CMakeLists.txt index 28cce946..ce9b34e2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -232,6 +232,7 @@ SET(OPENAL_OBJS OpenAL32/alAuxEffectSlot.c SET(ALC_OBJS Alc/ALc.c Alc/ALu.c Alc/alcConfig.c + Alc/alcReverb.c Alc/alcRing.c Alc/alcThread.c Alc/bs2b.c diff --git a/OpenAL32/Include/alAuxEffectSlot.h b/OpenAL32/Include/alAuxEffectSlot.h index 032933a2..cc5b9d34 100644 --- a/OpenAL32/Include/alAuxEffectSlot.h +++ b/OpenAL32/Include/alAuxEffectSlot.h @@ -4,6 +4,7 @@ #include "AL/al.h" #include "alEffect.h" #include "alFilter.h" +#include "alReverb.h" #ifdef __cplusplus extern "C" { @@ -22,15 +23,7 @@ typedef struct ALeffectslot ALfloat Gain; ALboolean AuxSendAuto; - ALfloat *ReverbBuffer; - // in frames! - ALuint ReverbLength; - ALuint ReverbPos; - ALuint ReverbReflectPos; - ALuint ReverbLatePos; - ALfloat ReverbDecayGain; - - FILTER iirFilter; + ALverbState *ReverbState; ALuint refcount; diff --git a/OpenAL32/Include/alReverb.h b/OpenAL32/Include/alReverb.h new file mode 100644 index 00000000..46c7194c --- /dev/null +++ b/OpenAL32/Include/alReverb.h @@ -0,0 +1,26 @@ +#ifndef _AL_REVERB_H_ +#define _AL_REVERB_H_ + +#include "AL/al.h" +#include "AL/alc.h" +#include "alMain.h" +#include "alAuxEffectSlot.h" +#include "alEffect.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct ALverbState ALverbState; + +ALverbState *VerbCreate(ALCcontext *Context); +ALvoid VerbDestroy(ALverbState *State); +ALvoid VerbUpdate(ALCcontext *Context, struct ALeffectslot *Slot, ALeffect *Effect); +ALvoid VerbProcess(ALverbState *State, ALuint SamplesToDo, const ALfloat *SamplesIn, ALfloat (*SamplesOut)[OUTPUTCHANNELS]); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/OpenAL32/alAuxEffectSlot.c b/OpenAL32/alAuxEffectSlot.c index 235d1b8d..dd524f02 100644 --- a/OpenAL32/alAuxEffectSlot.c +++ b/OpenAL32/alAuxEffectSlot.c @@ -29,6 +29,7 @@ #include "alAuxEffectSlot.h" #include "alThunk.h" #include "alError.h" +#include "alReverb.h" static ALvoid InitializeEffect(ALCcontext *Context, ALeffectslot *ALEffectSlot, ALeffect *effect); @@ -148,7 +149,7 @@ ALvoid AL_APIENTRY alDeleteAuxiliaryEffectSlots(ALsizei n, ALuint *effectslots) *list = (*list)->next; ALTHUNK_REMOVEENTRY(ALAuxiliaryEffectSlot->effectslot); - free(ALAuxiliaryEffectSlot->ReverbBuffer); + VerbDestroy(ALAuxiliaryEffectSlot->ReverbState); memset(ALAuxiliaryEffectSlot, 0, sizeof(ALeffectslot)); free(ALAuxiliaryEffectSlot); @@ -467,54 +468,20 @@ ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, A static ALvoid InitializeEffect(ALCcontext *Context, ALeffectslot *ALEffectSlot, ALeffect *effect) { - ALfloat *ptr = NULL; - if(!effect) { memset(&ALEffectSlot->effect, 0, sizeof(ALEffectSlot->effect)); - goto done; + VerbDestroy(ALEffectSlot->ReverbState); + ALEffectSlot->ReverbState = NULL; + return; } - if(effect->type == AL_EFFECT_REVERB) { - ALuint size; - ALfloat reverbwait; - - reverbwait = (1.0f-effect->Reverb.Density)*(0.1f-0.075f) + 0.075f; - - size = (ALuint)((ALfloat)Context->Frequency * - (effect->Reverb.ReflectionsDelay + - effect->Reverb.LateReverbDelay + - reverbwait)) + 1; - - ptr = calloc(size, sizeof(ALfloat)); - if(!ptr) - { - alSetError(AL_OUT_OF_MEMORY); - return; - } - if(ALEffectSlot->ReverbBuffer) - memcpy(ptr, ALEffectSlot->ReverbBuffer, min(size, ALEffectSlot->ReverbLength)*sizeof(ALfloat)); - ALEffectSlot->ReverbLength = size; - ALEffectSlot->ReverbPos %= size; - ALEffectSlot->ReverbReflectPos = (ALuint)(ALEffectSlot->ReverbLength - - ((ALfloat)Context->Frequency * - effect->Reverb.ReflectionsDelay) + - ALEffectSlot->ReverbPos) % - ALEffectSlot->ReverbLength; - ALEffectSlot->ReverbLatePos = (ALuint)(ALEffectSlot->ReverbLength - - ((ALfloat)Context->Frequency * - (effect->Reverb.LateReverbDelay + - effect->Reverb.ReflectionsDelay)) + - ALEffectSlot->ReverbPos) % - ALEffectSlot->ReverbLength; - ALEffectSlot->ReverbDecayGain = pow(1.0/32768.0, 1.0/(effect->Reverb.DecayTime/reverbwait)); + if(!ALEffectSlot->ReverbState) + ALEffectSlot->ReverbState = VerbCreate(Context); + VerbUpdate(Context, ALEffectSlot, effect); } - memcpy(&ALEffectSlot->effect, effect, sizeof(*effect)); -done: - free(ALEffectSlot->ReverbBuffer); - ALEffectSlot->ReverbBuffer = ptr; } @@ -531,7 +498,7 @@ ALvoid ReleaseALAuxiliaryEffectSlots(ALCcontext *Context) Context->AuxiliaryEffectSlot = Context->AuxiliaryEffectSlot->next; // Release effectslot structure - free(temp->ReverbBuffer); + VerbDestroy(temp->ReverbState); ALTHUNK_REMOVEENTRY(temp->effectslot); memset(temp, 0, sizeof(ALeffectslot)); -- 2.11.4.GIT