From 8627a92ea82ce53339ebc4fdb2e39ddcfa46d3cb Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Fri, 5 Feb 2016 08:14:41 -0800 Subject: Better organize the reverb code into separate labeled sections --- Alc/effects/reverb.c | 1779 +++++++++++++++++++++++++------------------------- 1 file changed, 898 insertions(+), 881 deletions(-) (limited to 'Alc/effects/reverb.c') diff --git a/Alc/effects/reverb.c b/Alc/effects/reverb.c index 7f4139c6..7911e43b 100644 --- a/Alc/effects/reverb.c +++ b/Alc/effects/reverb.c @@ -162,12 +162,29 @@ typedef struct ALreverbState { ALfloat EarlySamples[MAX_UPDATE_SAMPLES][4]; } ALreverbState; +static ALvoid ALreverbState_Destruct(ALreverbState *State) +{ + free(State->SampleBuffer); + State->SampleBuffer = NULL; +} + +static ALboolean ALreverbState_deviceUpdate(ALreverbState *State, ALCdevice *Device); +static ALvoid ALreverbState_update(ALreverbState *State, const ALCdevice *Device, const ALeffectslot *Slot); +static ALvoid ALreverbState_processStandard(ALreverbState *State, ALuint SamplesToDo, const ALfloat *restrict SamplesIn, ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALuint NumChannels); +static ALvoid ALreverbState_processEax(ALreverbState *State, ALuint SamplesToDo, const ALfloat *restrict SamplesIn, ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALuint NumChannels); +static ALvoid ALreverbState_process(ALreverbState *State, ALuint SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALuint NumChannels); +DECLARE_DEFAULT_ALLOCATORS(ALreverbState) + +DEFINE_ALEFFECTSTATE_VTABLE(ALreverbState); + /* This is a user config option for modifying the overall output of the reverb * effect. */ ALfloat ReverbBoost = 1.0f; -/* Specifies whether to use a standard reverb effect in place of EAX reverb */ +/* Specifies whether to use a standard reverb effect in place of EAX reverb (no + * high-pass, modulation, or echo). + */ ALboolean EmulateEAXReverb = AL_FALSE; /* This coefficient is used to define the maximum frequency range controlled @@ -221,1080 +238,1080 @@ static const ALfloat LATE_LINE_LENGTH[4] = static const ALfloat LATE_LINE_MULTIPLIER = 4.0f; -// Basic delay line input/output routines. -static inline ALfloat DelayLineOut(DelayLine *Delay, ALuint offset) +/************************************** + * Device Update * + **************************************/ + +// Given the allocated sample buffer, this function updates each delay line +// offset. +static inline ALvoid RealizeLineOffset(ALfloat *sampleBuffer, DelayLine *Delay) { - return Delay->Line[offset&Delay->Mask]; + Delay->Line = &sampleBuffer[(ptrdiff_t)Delay->Line]; } -static inline ALvoid DelayLineIn(DelayLine *Delay, ALuint offset, ALfloat in) +// Calculate the length of a delay line and store its mask and offset. +static ALuint CalcLineLength(ALfloat length, ptrdiff_t offset, ALuint frequency, ALuint extra, DelayLine *Delay) { - Delay->Line[offset&Delay->Mask] = in; + ALuint samples; + + // All line lengths are powers of 2, calculated from their lengths, with + // an additional sample in case of rounding errors. + samples = fastf2u(length*frequency) + extra; + samples = NextPowerOf2(samples + 1); + // All lines share a single sample buffer. + Delay->Mask = samples - 1; + Delay->Line = (ALfloat*)offset; + // Return the sample count for accumulation. + return samples; } -// Given an input sample, this function produces modulation for the late -// reverb. -static inline ALfloat EAXModulation(ALreverbState *State, ALuint offset, ALfloat in) +/* Calculates the delay line metrics and allocates the shared sample buffer + * for all lines given the sample rate (frequency). If an allocation failure + * occurs, it returns AL_FALSE. + */ +static ALboolean AllocLines(ALuint frequency, ALreverbState *State) { - ALfloat sinus, frac, fdelay; - ALfloat out0, out1; - ALuint delay; - - // Calculate the sinus rythm (dependent on modulation time and the - // sampling rate). The center of the sinus is moved to reduce the delay - // of the effect when the time or depth are low. - sinus = 1.0f - cosf(F_TAU * State->Mod.Index / State->Mod.Range); + ALuint totalSamples, index; + ALfloat length; + ALfloat *newBuffer = NULL; - // Step the modulation index forward, keeping it bound to its range. - State->Mod.Index = (State->Mod.Index + 1) % State->Mod.Range; + // All delay line lengths are calculated to accomodate the full range of + // lengths given their respective paramters. + totalSamples = 0; - // The depth determines the range over which to read the input samples - // from, so it must be filtered to reduce the distortion caused by even - // small parameter changes. - State->Mod.Filter = lerp(State->Mod.Filter, State->Mod.Depth, - State->Mod.Coeff); + /* The modulator's line length is calculated from the maximum modulation + * time and depth coefficient, and halfed for the low-to-high frequency + * swing. An additional sample is added to keep it stable when there is no + * modulation. + */ + length = (AL_EAXREVERB_MAX_MODULATION_TIME*MODULATION_DEPTH_COEFF/2.0f); + totalSamples += CalcLineLength(length, totalSamples, frequency, 1, + &State->Mod.Delay); - // Calculate the read offset and fraction between it and the next sample. - frac = modff(State->Mod.Filter*sinus + 1.0f, &fdelay); - delay = fastf2u(fdelay); + // The initial delay is the sum of the reflections and late reverb + // delays. This must include space for storing a loop update to feed the + // early reflections, decorrelator, and echo. + length = AL_EAXREVERB_MAX_REFLECTIONS_DELAY + + AL_EAXREVERB_MAX_LATE_REVERB_DELAY; + totalSamples += CalcLineLength(length, totalSamples, frequency, + MAX_UPDATE_SAMPLES, &State->Delay); - // Get the two samples crossed by the offset, and feed the delay line - // with the next input sample. - out0 = DelayLineOut(&State->Mod.Delay, offset - delay); - out1 = DelayLineOut(&State->Mod.Delay, offset - delay - 1); - DelayLineIn(&State->Mod.Delay, offset, in); + // The early reflection lines. + for(index = 0;index < 4;index++) + totalSamples += CalcLineLength(EARLY_LINE_LENGTH[index], totalSamples, + frequency, 0, &State->Early.Delay[index]); - // The output is obtained by linearly interpolating the two samples that - // were acquired above. - return lerp(out0, out1, frac); -} + // The decorrelator line is calculated from the lowest reverb density (a + // parameter value of 1). This must include space for storing a loop update + // to feed the late reverb. + length = (DECO_FRACTION * DECO_MULTIPLIER * DECO_MULTIPLIER) * + LATE_LINE_LENGTH[0] * (1.0f + LATE_LINE_MULTIPLIER); + totalSamples += CalcLineLength(length, totalSamples, frequency, MAX_UPDATE_SAMPLES, + &State->Decorrelator); -// Given some input sample, this function produces four-channel outputs for the -// early reflections. -static inline ALvoid EarlyReflection(ALreverbState *State, ALuint todo, ALfloat (*restrict out)[4]) -{ - ALfloat d[4], v, f[4]; - ALuint i; + // The late all-pass lines. + for(index = 0;index < 4;index++) + totalSamples += CalcLineLength(ALLPASS_LINE_LENGTH[index], totalSamples, + frequency, 0, &State->Late.ApDelay[index]); - for(i = 0;i < todo;i++) + // The late delay lines are calculated from the lowest reverb density. + for(index = 0;index < 4;index++) { - ALuint offset = State->Offset+i; + length = LATE_LINE_LENGTH[index] * (1.0f + LATE_LINE_MULTIPLIER); + totalSamples += CalcLineLength(length, totalSamples, frequency, 0, + &State->Late.Delay[index]); + } - // Obtain the decayed results of each early delay line. - d[0] = DelayLineOut(&State->Early.Delay[0], offset-State->Early.Offset[0]) * State->Early.Coeff[0]; - d[1] = DelayLineOut(&State->Early.Delay[1], offset-State->Early.Offset[1]) * State->Early.Coeff[1]; - d[2] = DelayLineOut(&State->Early.Delay[2], offset-State->Early.Offset[2]) * State->Early.Coeff[2]; - d[3] = DelayLineOut(&State->Early.Delay[3], offset-State->Early.Offset[3]) * State->Early.Coeff[3]; + // The echo all-pass and delay lines. + totalSamples += CalcLineLength(ECHO_ALLPASS_LENGTH, totalSamples, + frequency, 0, &State->Echo.ApDelay); + totalSamples += CalcLineLength(AL_EAXREVERB_MAX_ECHO_TIME, totalSamples, + frequency, 0, &State->Echo.Delay); - /* 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 feed-back delay network (FDN). - * N - * --- - * \ - * v = 2/N / d_i - * --- - * i=1 - */ - v = (d[0] + d[1] + d[2] + d[3]) * 0.5f; - // The junction is loaded with the input here. - v += DelayLineOut(&State->Delay, offset-State->DelayTap[0]); + if(totalSamples != State->TotalSamples) + { + TRACE("New reverb buffer length: %u samples (%f sec)\n", totalSamples, totalSamples/(float)frequency); + newBuffer = realloc(State->SampleBuffer, sizeof(ALfloat) * totalSamples); + if(newBuffer == NULL) + return AL_FALSE; + State->SampleBuffer = newBuffer; + State->TotalSamples = totalSamples; + } - // 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]; + // Update all delays to reflect the new sample buffer. + RealizeLineOffset(State->SampleBuffer, &State->Delay); + RealizeLineOffset(State->SampleBuffer, &State->Decorrelator); + for(index = 0;index < 4;index++) + { + RealizeLineOffset(State->SampleBuffer, &State->Early.Delay[index]); + RealizeLineOffset(State->SampleBuffer, &State->Late.ApDelay[index]); + RealizeLineOffset(State->SampleBuffer, &State->Late.Delay[index]); + } + RealizeLineOffset(State->SampleBuffer, &State->Mod.Delay); + RealizeLineOffset(State->SampleBuffer, &State->Echo.ApDelay); + RealizeLineOffset(State->SampleBuffer, &State->Echo.Delay); - // Re-feed the delay lines. - DelayLineIn(&State->Early.Delay[0], offset, f[0]); - DelayLineIn(&State->Early.Delay[1], offset, f[1]); - DelayLineIn(&State->Early.Delay[2], offset, f[2]); - DelayLineIn(&State->Early.Delay[3], offset, f[3]); + // Clear the sample buffer. + for(index = 0;index < State->TotalSamples;index++) + State->SampleBuffer[index] = 0.0f; - // Output the results of the junction for all four channels. - out[i][0] = State->Early.Gain * f[0]; - out[i][1] = State->Early.Gain * f[1]; - out[i][2] = State->Early.Gain * f[2]; - out[i][3] = State->Early.Gain * f[3]; - } + return AL_TRUE; } -// Basic attenuated all-pass input/output routine. -static inline ALfloat AllpassInOut(DelayLine *Delay, ALuint outOffset, ALuint inOffset, ALfloat in, ALfloat feedCoeff, ALfloat coeff) +static ALboolean ALreverbState_deviceUpdate(ALreverbState *State, ALCdevice *Device) { - ALfloat out, feed; + ALuint frequency = Device->Frequency, index; - out = DelayLineOut(Delay, outOffset); - feed = feedCoeff * in; - DelayLineIn(Delay, inOffset, (feedCoeff * (out - feed)) + in); + // Allocate the delay lines. + if(!AllocLines(frequency, State)) + return AL_FALSE; - // The time-based attenuation is only applied to the delay output to - // keep it from affecting the feed-back path (which is already controlled - // by the all-pass feed coefficient). - return (coeff * out) - feed; + // Calculate the modulation filter coefficient. Notice that the exponent + // is calculated given the current sample rate. This ensures that the + // resulting filter response over time is consistent across all sample + // rates. + State->Mod.Coeff = powf(MODULATION_FILTER_COEFF, + MODULATION_FILTER_CONST / frequency); + + // The early reflection and late all-pass filter line lengths are static, + // so their offsets only need to be calculated once. + for(index = 0;index < 4;index++) + { + State->Early.Offset[index] = fastf2u(EARLY_LINE_LENGTH[index] * frequency); + State->Late.ApOffset[index] = fastf2u(ALLPASS_LINE_LENGTH[index] * frequency); + } + + // The echo all-pass filter line length is static, so its offset only + // needs to be calculated once. + State->Echo.ApOffset = fastf2u(ECHO_ALLPASS_LENGTH * frequency); + + return AL_TRUE; } -// All-pass input/output routine for late reverb. -static inline ALfloat LateAllPassInOut(ALreverbState *State, ALuint offset, ALuint index, ALfloat in) +/************************************** + * Effect Update * + **************************************/ + +// Calculate a decay coefficient given the length of each cycle and the time +// until the decay reaches -60 dB. +static inline ALfloat CalcDecayCoeff(ALfloat length, ALfloat decayTime) { - return AllpassInOut(&State->Late.ApDelay[index], - offset - State->Late.ApOffset[index], - offset, in, State->Late.ApFeedCoeff, - State->Late.ApCoeff[index]); + return powf(0.001f/*-60 dB*/, length/decayTime); } -// Low-pass filter input/output routine for late reverb. -static inline ALfloat LateLowPassInOut(ALreverbState *State, ALuint index, ALfloat in) +// Calculate a decay length from a coefficient and the time until the decay +// reaches -60 dB. +static inline ALfloat CalcDecayLength(ALfloat coeff, ALfloat decayTime) { - in = lerp(in, State->Late.LpSample[index], State->Late.LpCoeff[index]); - State->Late.LpSample[index] = in; - return in; + return log10f(coeff) * decayTime / log10f(0.001f)/*-60 dB*/; } -// Given four decorrelated input samples, this function produces four-channel -// output for the late reverb. -static inline ALvoid LateReverb(ALreverbState *State, ALuint todo, ALfloat (*restrict out)[4]) +// Calculate an attenuation to be applied to the input of any echo models to +// compensate for modal density and decay time. +static inline ALfloat CalcDensityGain(ALfloat a) { - ALfloat d[4], f[4]; - ALuint i; - - for(i = 0;i < todo;i++) - { - ALuint offset = State->Offset+i; - - f[0] = DelayLineOut(&State->Decorrelator, offset); - f[1] = DelayLineOut(&State->Decorrelator, offset-State->DecoTap[0]); - f[2] = DelayLineOut(&State->Decorrelator, offset-State->DecoTap[1]); - f[3] = DelayLineOut(&State->Decorrelator, offset-State->DecoTap[2]); - - // Obtain the decayed results of the cyclical delay lines, and add the - // corresponding input channels. Then pass the results through the - // low-pass filters. - f[0] += DelayLineOut(&State->Late.Delay[0], offset-State->Late.Offset[0]) * State->Late.Coeff[0]; - f[1] += DelayLineOut(&State->Late.Delay[1], offset-State->Late.Offset[1]) * State->Late.Coeff[1]; - f[2] += DelayLineOut(&State->Late.Delay[2], offset-State->Late.Offset[2]) * State->Late.Coeff[2]; - f[3] += DelayLineOut(&State->Late.Delay[3], offset-State->Late.Offset[3]) * State->Late.Coeff[3]; + /* The energy of a signal can be obtained by finding the area under the + * squared signal. This takes the form of Sum(x_n^2), where x is the + * amplitude for the sample n. + * + * Decaying feedback matches exponential decay of the form Sum(a^n), + * where a is the attenuation coefficient, and n is the sample. The area + * under this decay curve can be calculated as: 1 / (1 - a). + * + * Modifying the above equation to find the squared area under the curve + * (for energy) yields: 1 / (1 - a^2). Input attenuation can then be + * calculated by inverting the square root of this approximation, + * yielding: 1 / sqrt(1 / (1 - a^2)), simplified to: sqrt(1 - a^2). + */ + return sqrtf(1.0f - (a * a)); +} - // This is where the feed-back cycles from line 0 to 1 to 3 to 2 and - // back to 0. - d[0] = LateLowPassInOut(State, 2, f[2]); - d[1] = LateLowPassInOut(State, 0, f[0]); - d[2] = LateLowPassInOut(State, 3, f[3]); - d[3] = LateLowPassInOut(State, 1, f[1]); +// Calculate the mixing matrix coefficients given a diffusion factor. +static inline ALvoid CalcMatrixCoeffs(ALfloat diffusion, ALfloat *x, ALfloat *y) +{ + ALfloat n, t; - // To help increase diffusion, run each line through an all-pass filter. - // When there is no diffusion, the shortest all-pass filter will feed - // the shortest delay line. - d[0] = LateAllPassInOut(State, offset, 0, d[0]); - d[1] = LateAllPassInOut(State, offset, 1, d[1]); - d[2] = LateAllPassInOut(State, offset, 2, d[2]); - d[3] = LateAllPassInOut(State, offset, 3, d[3]); + // The matrix is of order 4, so n is sqrt (4 - 1). + n = sqrtf(3.0f); + t = diffusion * atanf(n); - /* Late reverb is done with a modified feed-back delay network (FDN) - * topology. Four input lines are each fed through their own all-pass - * filter and then into the mixing matrix. The four outputs of the - * mixing matrix are then cycled back to the inputs. Each output feeds - * a different input to form a circlular feed cycle. - * - * The mixing matrix used is a 4D skew-symmetric rotation matrix - * derived using a single unitary rotational parameter: - * - * [ d, a, b, c ] 1 = a^2 + b^2 + c^2 + d^2 - * [ -a, d, c, -b ] - * [ -b, -c, d, a ] - * [ -c, b, -a, d ] - * - * The rotation is constructed from the effect's diffusion parameter, - * yielding: 1 = x^2 + 3 y^2; where a, b, and c are the coefficient y - * with differing signs, and d is the coefficient x. The matrix is - * thus: - * - * [ x, y, -y, y ] n = sqrt(matrix_order - 1) - * [ -y, x, y, y ] t = diffusion_parameter * atan(n) - * [ y, -y, x, y ] x = cos(t) - * [ -y, -y, -y, x ] y = sin(t) / n - * - * To reduce the number of multiplies, the x coefficient is applied - * with the cyclical delay line coefficients. Thus only the y - * coefficient is applied when mixing, and is modified to be: y / x. - */ - f[0] = d[0] + (State->Late.MixCoeff * ( d[1] + -d[2] + d[3])); - f[1] = d[1] + (State->Late.MixCoeff * (-d[0] + d[2] + d[3])); - f[2] = d[2] + (State->Late.MixCoeff * ( d[0] + -d[1] + d[3])); - f[3] = d[3] + (State->Late.MixCoeff * (-d[0] + -d[1] + -d[2] )); + // Calculate the first mixing matrix coefficient. + *x = cosf(t); + // Calculate the second mixing matrix coefficient. + *y = sinf(t) / n; +} - // Output the results of the matrix for all four channels, attenuated by - // the late reverb gain (which is attenuated by the 'x' mix coefficient). - // Mix early reflections and late reverb. - out[i][0] = State->Late.Gain * f[0]; - out[i][1] = State->Late.Gain * f[1]; - out[i][2] = State->Late.Gain * f[2]; - out[i][3] = State->Late.Gain * f[3]; +// Calculate the limited HF ratio for use with the late reverb low-pass +// filters. +static ALfloat CalcLimitedHfRatio(ALfloat hfRatio, ALfloat airAbsorptionGainHF, ALfloat decayTime) +{ + ALfloat limitRatio; - // Re-feed the cyclical delay lines. - DelayLineIn(&State->Late.Delay[0], offset, f[0]); - DelayLineIn(&State->Late.Delay[1], offset, f[1]); - DelayLineIn(&State->Late.Delay[2], offset, f[2]); - DelayLineIn(&State->Late.Delay[3], offset, f[3]); - } + /* 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 / (CalcDecayLength(airAbsorptionGainHF, decayTime) * + SPEEDOFSOUNDMETRESPERSEC); + /* Using the limit calculated above, apply the upper bound to the HF + * ratio. Also need to limit the result to a minimum of 0.1, just like the + * HF ratio parameter. */ + return clampf(limitRatio, 0.1f, hfRatio); } -// Given an input sample, this function mixes echo into the four-channel late -// reverb. -static inline ALvoid EAXEcho(ALreverbState *State, ALuint todo, ALfloat (*restrict late)[4]) +// Calculate the coefficient for a HF (and eventually LF) decay damping +// filter. +static inline ALfloat CalcDampingCoeff(ALfloat hfRatio, ALfloat length, ALfloat decayTime, ALfloat decayCoeff, ALfloat cw) { - ALfloat out, feed; - ALuint i; + ALfloat coeff, g; - for(i = 0;i < todo;i++) + // Eventually this should boost the high frequencies when the ratio + // exceeds 1. + coeff = 0.0f; + if (hfRatio < 1.0f) { - ALuint offset = State->Offset+i; - - // Get the latest attenuated echo sample for output. - feed = DelayLineOut(&State->Echo.Delay, offset-State->Echo.Offset) * - State->Echo.Coeff; - - // Mix the output into the late reverb channels. - out = State->Echo.MixCoeff * feed; - late[i][0] += out; - late[i][1] += out; - late[i][2] += out; - late[i][3] += out; - - // Mix the energy-attenuated input with the output and pass it through - // the echo low-pass filter. - feed += DelayLineOut(&State->Delay, offset-State->DelayTap[1]) * - State->Echo.DensityGain; - feed = lerp(feed, State->Echo.LpSample, State->Echo.LpCoeff); - State->Echo.LpSample = feed; + // Calculate the low-pass coefficient by dividing the HF decay + // coefficient by the full decay coefficient. + g = CalcDecayCoeff(length, decayTime * hfRatio) / decayCoeff; - // Then the echo all-pass filter. - feed = AllpassInOut(&State->Echo.ApDelay, offset-State->Echo.ApOffset, - offset, feed, State->Echo.ApFeedCoeff, - State->Echo.ApCoeff); + // Damping is done with a 1-pole filter, so g needs to be squared. + g *= g; + if(g < 0.9999f) /* 1-epsilon */ + { + /* Be careful with gains < 0.001, as that causes the coefficient + * head towards 1, which will flatten the signal. */ + g = maxf(g, 0.001f); + coeff = (1 - g*cw - sqrtf(2*g*(1-cw) - g*g*(1 - cw*cw))) / + (1 - g); + } - // Feed the delay with the mixed and filtered sample. - DelayLineIn(&State->Echo.Delay, offset, feed); + // Very low decay times will produce minimal output, so apply an + // upper bound to the coefficient. + coeff = minf(coeff, 0.98f); } + return coeff; } -// Perform the non-EAX reverb pass on a given input sample, resulting in -// four-channel output. -static inline ALvoid VerbPass(ALreverbState *State, ALuint todo, const ALfloat *in, ALfloat (*restrict early)[4], ALfloat (*restrict late)[4]) +// Update the EAX modulation index, range, and depth. Keep in mind that this +// kind of vibrato is additive and not multiplicative as one may expect. The +// downswing will sound stronger than the upswing. +static ALvoid UpdateModulator(ALfloat modTime, ALfloat modDepth, ALuint frequency, ALreverbState *State) { - ALuint i; - - // Low-pass filter the incoming samples. - for(i = 0;i < todo;i++) - DelayLineIn(&State->Delay, State->Offset+i, - ALfilterState_processSingle(&State->LpFilter, in[i]) - ); - - // Calculate the early reflection from the first delay tap. - EarlyReflection(State, todo, early); + ALuint range; - // Feed the decorrelator from the energy-attenuated output of the second - // delay tap. - for(i = 0;i < todo;i++) - { - ALuint offset = State->Offset+i; - ALfloat sample = DelayLineOut(&State->Delay, offset - State->DelayTap[1]) * - State->Late.DensityGain; - DelayLineIn(&State->Decorrelator, offset, sample); - } + /* Modulation is calculated in two parts. + * + * The modulation time effects the sinus applied to the change in + * frequency. An index out of the current time range (both in samples) + * is incremented each sample. The range is bound to a reasonable + * minimum (1 sample) and when the timing changes, the index is rescaled + * to the new range (to keep the sinus consistent). + */ + range = maxu(fastf2u(modTime*frequency), 1); + State->Mod.Index = (ALuint)(State->Mod.Index * (ALuint64)range / + State->Mod.Range); + State->Mod.Range = range; - // Calculate the late reverb from the decorrelator taps. - LateReverb(State, todo, late); + /* The modulation depth effects the amount of frequency change over the + * range of the sinus. It needs to be scaled by the modulation time so + * that a given depth produces a consistent change in frequency over all + * ranges of time. Since the depth is applied to a sinus value, it needs + * to be halfed once for the sinus range and again for the sinus swing + * in time (half of it is spent decreasing the frequency, half is spent + * increasing it). + */ + State->Mod.Depth = modDepth * MODULATION_DEPTH_COEFF * modTime / 2.0f / + 2.0f * frequency; +} - // Step all delays forward one sample. - State->Offset += todo; +// Update the offsets for the initial effect delay line. +static ALvoid UpdateDelayLine(ALfloat earlyDelay, ALfloat lateDelay, ALuint frequency, ALreverbState *State) +{ + // Calculate the initial delay taps. + State->DelayTap[0] = fastf2u(earlyDelay * frequency); + State->DelayTap[1] = fastf2u((earlyDelay + lateDelay) * frequency); } -// Perform the EAX reverb pass on a given input sample, resulting in four- -// channel output. -static inline ALvoid EAXVerbPass(ALreverbState *State, ALuint todo, const ALfloat *input, ALfloat (*restrict early)[4], ALfloat (*restrict late)[4]) +// Update the early reflections gain and line coefficients. +static ALvoid UpdateEarlyLines(ALfloat earlyGain, ALfloat lateDelay, ALreverbState *State) { - ALuint i; + ALuint index; - // Band-pass and modulate the incoming samples. - for(i = 0;i < todo;i++) - { - ALfloat sample = input[i]; - sample = ALfilterState_processSingle(&State->LpFilter, sample); - sample = ALfilterState_processSingle(&State->HpFilter, sample); + // Calculate the early reflections gain (from the master effect gain, and + // reflections gain parameters) with a constant attenuation of 0.5. + State->Early.Gain = 0.5f * earlyGain; - // Perform any modulation on the input. - sample = EAXModulation(State, State->Offset+i, sample); + // Calculate the gain (coefficient) for each early delay line using the + // late delay time. This expands the early reflections to the start of + // the late reverb. + for(index = 0;index < 4;index++) + State->Early.Coeff[index] = CalcDecayCoeff(EARLY_LINE_LENGTH[index], + lateDelay); +} - // Feed the initial delay line. - DelayLineIn(&State->Delay, State->Offset+i, sample); - } +// Update the offsets for the decorrelator line. +static ALvoid UpdateDecorrelator(ALfloat density, ALuint frequency, ALreverbState *State) +{ + ALuint index; + ALfloat length; - // Calculate the early reflection from the first delay tap. - EarlyReflection(State, todo, early); - - // Feed the decorrelator from the energy-attenuated output of the second - // delay tap. - for(i = 0;i < todo;i++) + /* The late reverb inputs are decorrelated to smooth the reverb tail and + * reduce harsh echos. The first tap occurs immediately, while the + * remaining taps are delayed by multiples of a fraction of the smallest + * cyclical delay time. + * + * offset[index] = (FRACTION (MULTIPLIER^index)) smallest_delay + */ + for(index = 0;index < 3;index++) { - ALuint offset = State->Offset+i; - ALfloat sample = DelayLineOut(&State->Delay, offset - State->DelayTap[1]) * - State->Late.DensityGain; - DelayLineIn(&State->Decorrelator, offset, sample); + length = (DECO_FRACTION * powf(DECO_MULTIPLIER, (ALfloat)index)) * + LATE_LINE_LENGTH[0] * (1.0f + (density * LATE_LINE_MULTIPLIER)); + State->DecoTap[index] = fastf2u(length * frequency); } - - // Calculate the late reverb from the decorrelator taps. - LateReverb(State, todo, late); - - // Calculate and mix in any echo. - EAXEcho(State, todo, late); - - // Step all delays forward. - State->Offset += todo; } -static ALvoid ALreverbState_processStandard(ALreverbState *State, ALuint SamplesToDo, const ALfloat *restrict SamplesIn, ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALuint NumChannels) +// Update the late reverb gains, line lengths, and line coefficients. +static ALvoid UpdateLateLines(ALfloat lateGain, ALfloat xMix, ALfloat density, ALfloat decayTime, ALfloat diffusion, ALfloat echoDepth, ALfloat hfRatio, ALfloat cw, ALuint frequency, ALreverbState *State) { - ALfloat (*restrict early)[4] = State->EarlySamples; - ALfloat (*restrict late)[4] = State->ReverbSamples; - ALuint index, c, i, l; - ALfloat gain; + ALfloat length; + ALuint index; - /* Process reverb for these samples. */ - for(index = 0;index < SamplesToDo;) - { - ALuint todo = minu(SamplesToDo-index, MAX_UPDATE_SAMPLES); + /* Calculate the late reverb gain (from the master effect gain, and late + * reverb gain parameters). Since the output is tapped prior to the + * application of the next delay line coefficients, this gain needs to be + * attenuated by the 'x' mixing matrix coefficient as well. Also attenuate + * the late reverb when echo depth is high and diffusion is low, so the + * echo is slightly stronger than the decorrelated echos in the reverb + * tail. + */ + State->Late.Gain = lateGain * xMix * (1.0f - (echoDepth*0.5f*(1.0f - diffusion))); - VerbPass(State, todo, &SamplesIn[index], early, late); + /* To compensate for changes in modal density and decay time of the late + * reverb signal, the input is attenuated based on the maximal energy of + * the outgoing signal. This approximation is used to keep the apparent + * energy of the signal equal for all ranges of density and decay time. + * + * The average length of the cyclcical delay lines is used to calculate + * the attenuation coefficient. + */ + length = (LATE_LINE_LENGTH[0] + LATE_LINE_LENGTH[1] + + LATE_LINE_LENGTH[2] + LATE_LINE_LENGTH[3]) / 4.0f; + length *= 1.0f + (density * LATE_LINE_MULTIPLIER); + State->Late.DensityGain = CalcDensityGain( + CalcDecayCoeff(length, decayTime) + ); - for(l = 0;l < 4;l++) - { - for(c = 0;c < NumChannels;c++) - { - gain = State->Early.PanGain[l][c]; - if(fabsf(gain) > GAIN_SILENCE_THRESHOLD) - { - for(i = 0;i < todo;i++) - SamplesOut[c][index+i] += gain*early[i][l]; - } - gain = State->Late.PanGain[l][c]; - if(fabsf(gain) > GAIN_SILENCE_THRESHOLD) - { - for(i = 0;i < todo;i++) - SamplesOut[c][index+i] += gain*late[i][l]; - } - } - } + // Calculate the all-pass feed-back and feed-forward coefficient. + State->Late.ApFeedCoeff = 0.5f * powf(diffusion, 2.0f); - index += todo; - } -} + for(index = 0;index < 4;index++) + { + // Calculate the gain (coefficient) for each all-pass line. + State->Late.ApCoeff[index] = CalcDecayCoeff( + ALLPASS_LINE_LENGTH[index], decayTime + ); -static ALvoid ALreverbState_processEax(ALreverbState *State, ALuint SamplesToDo, const ALfloat *restrict SamplesIn, ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALuint NumChannels) -{ - ALfloat (*restrict early)[4] = State->EarlySamples; - ALfloat (*restrict late)[4] = State->ReverbSamples; - ALuint index, c, i, l; - ALfloat gain; + // Calculate the length (in seconds) of each cyclical delay line. + length = LATE_LINE_LENGTH[index] * + (1.0f + (density * LATE_LINE_MULTIPLIER)); - /* Process reverb for these samples. */ - for(index = 0;index < SamplesToDo;) - { - ALuint todo = minu(SamplesToDo-index, MAX_UPDATE_SAMPLES); + // Calculate the delay offset for each cyclical delay line. + State->Late.Offset[index] = fastf2u(length * frequency); - EAXVerbPass(State, todo, &SamplesIn[index], early, late); + // Calculate the gain (coefficient) for each cyclical line. + State->Late.Coeff[index] = CalcDecayCoeff(length, decayTime); - for(l = 0;l < 4;l++) - { - for(c = 0;c < NumChannels;c++) - { - gain = State->Early.PanGain[l][c]; - if(fabsf(gain) > GAIN_SILENCE_THRESHOLD) - { - for(i = 0;i < todo;i++) - SamplesOut[c][index+i] += gain*early[i][l]; - } - gain = State->Late.PanGain[l][c]; - if(fabsf(gain) > GAIN_SILENCE_THRESHOLD) - { - for(i = 0;i < todo;i++) - SamplesOut[c][index+i] += gain*late[i][l]; - } - } - } + // Calculate the damping coefficient for each low-pass filter. + State->Late.LpCoeff[index] = CalcDampingCoeff( + hfRatio, length, decayTime, State->Late.Coeff[index], cw + ); - index += todo; + // Attenuate the cyclical line coefficients by the mixing coefficient + // (x). + State->Late.Coeff[index] *= xMix; } } -static ALvoid ALreverbState_process(ALreverbState *State, ALuint SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALuint NumChannels) +// Update the echo gain, line offset, line coefficients, and mixing +// coefficients. +static ALvoid UpdateEchoLine(ALfloat lateGain, ALfloat echoTime, ALfloat decayTime, ALfloat diffusion, ALfloat echoDepth, ALfloat hfRatio, ALfloat cw, ALuint frequency, ALreverbState *State) { - if(State->IsEax) - ALreverbState_processEax(State, SamplesToDo, SamplesIn[0], SamplesOut, NumChannels); - else - ALreverbState_processStandard(State, SamplesToDo, SamplesIn[0], SamplesOut, NumChannels); -} + // Update the offset and coefficient for the echo delay line. + State->Echo.Offset = fastf2u(echoTime * frequency); -// Given the allocated sample buffer, this function updates each delay line -// offset. -static inline ALvoid RealizeLineOffset(ALfloat *sampleBuffer, DelayLine *Delay) -{ - Delay->Line = &sampleBuffer[(ptrdiff_t)Delay->Line]; -} + // Calculate the decay coefficient for the echo line. + State->Echo.Coeff = CalcDecayCoeff(echoTime, decayTime); -// Calculate the length of a delay line and store its mask and offset. -static ALuint CalcLineLength(ALfloat length, ptrdiff_t offset, ALuint frequency, ALuint extra, DelayLine *Delay) -{ - ALuint samples; + // Calculate the energy-based attenuation coefficient for the echo delay + // line. + State->Echo.DensityGain = CalcDensityGain(State->Echo.Coeff); - // All line lengths are powers of 2, calculated from their lengths, with - // an additional sample in case of rounding errors. - samples = fastf2u(length*frequency) + extra; - samples = NextPowerOf2(samples + 1); - // All lines share a single sample buffer. - Delay->Mask = samples - 1; - Delay->Line = (ALfloat*)offset; - // Return the sample count for accumulation. - return samples; -} + // Calculate the echo all-pass feed coefficient. + State->Echo.ApFeedCoeff = 0.5f * powf(diffusion, 2.0f); -/* Calculates the delay line metrics and allocates the shared sample buffer - * for all lines given the sample rate (frequency). If an allocation failure - * occurs, it returns AL_FALSE. - */ -static ALboolean AllocLines(ALuint frequency, ALreverbState *State) -{ - ALuint totalSamples, index; - ALfloat length; - ALfloat *newBuffer = NULL; + // Calculate the echo all-pass attenuation coefficient. + State->Echo.ApCoeff = CalcDecayCoeff(ECHO_ALLPASS_LENGTH, decayTime); - // All delay line lengths are calculated to accomodate the full range of - // lengths given their respective paramters. - totalSamples = 0; + // Calculate the damping coefficient for each low-pass filter. + State->Echo.LpCoeff = CalcDampingCoeff(hfRatio, echoTime, decayTime, + State->Echo.Coeff, cw); - /* The modulator's line length is calculated from the maximum modulation - * time and depth coefficient, and halfed for the low-to-high frequency - * swing. An additional sample is added to keep it stable when there is no - * modulation. + /* Calculate the echo mixing coefficient. This is applied to the output mix + * only, not the feedback. */ - length = (AL_EAXREVERB_MAX_MODULATION_TIME*MODULATION_DEPTH_COEFF/2.0f); - totalSamples += CalcLineLength(length, totalSamples, frequency, 1, - &State->Mod.Delay); + State->Echo.MixCoeff = lateGain * echoDepth; +} - // The initial delay is the sum of the reflections and late reverb - // delays. This must include space for storing a loop update to feed the - // early reflections, decorrelator, and echo. - length = AL_EAXREVERB_MAX_REFLECTIONS_DELAY + - AL_EAXREVERB_MAX_LATE_REVERB_DELAY; - totalSamples += CalcLineLength(length, totalSamples, frequency, - MAX_UPDATE_SAMPLES, &State->Delay); +// Update the early and late 3D panning gains. +static ALvoid UpdateDirectPanning(const ALCdevice *Device, const ALfloat *ReflectionsPan, const ALfloat *LateReverbPan, ALfloat Gain, ALreverbState *State) +{ + ALfloat AmbientGains[MAX_OUTPUT_CHANNELS]; + ALfloat DirGains[MAX_OUTPUT_CHANNELS]; + ALfloat coeffs[MAX_AMBI_COEFFS]; + ALfloat length; + ALuint i; - // The early reflection lines. - for(index = 0;index < 4;index++) - totalSamples += CalcLineLength(EARLY_LINE_LENGTH[index], totalSamples, - frequency, 0, &State->Early.Delay[index]); + ComputeAmbientGains(Device->AmbiCoeffs, Device->NumChannels, Gain, AmbientGains); - // The decorrelator line is calculated from the lowest reverb density (a - // parameter value of 1). This must include space for storing a loop update - // to feed the late reverb. - length = (DECO_FRACTION * DECO_MULTIPLIER * DECO_MULTIPLIER) * - LATE_LINE_LENGTH[0] * (1.0f + LATE_LINE_MULTIPLIER); - totalSamples += CalcLineLength(length, totalSamples, frequency, MAX_UPDATE_SAMPLES, - &State->Decorrelator); + memset(State->Early.PanGain, 0, sizeof(State->Early.PanGain)); + length = sqrtf(ReflectionsPan[0]*ReflectionsPan[0] + ReflectionsPan[1]*ReflectionsPan[1] + ReflectionsPan[2]*ReflectionsPan[2]); + if(!(length > FLT_EPSILON)) + { + for(i = 0;i < Device->NumChannels;i++) + { + if(Device->ChannelName[i] == LFE) + continue; + State->Early.PanGain[i&3][i] = AmbientGains[i]; + } + } + else + { + /* Note that EAX Reverb's panning vectors are using right-handed + * coordinates, rather that the OpenAL's left-handed coordinates. + * Negate Z to fix this. + */ + ALfloat pan[3] = { + ReflectionsPan[0] / length, + ReflectionsPan[1] / length, + -ReflectionsPan[2] / length, + }; + length = minf(length, 1.0f); - // The late all-pass lines. - for(index = 0;index < 4;index++) - totalSamples += CalcLineLength(ALLPASS_LINE_LENGTH[index], totalSamples, - frequency, 0, &State->Late.ApDelay[index]); + CalcDirectionCoeffs(pan, coeffs); + ComputePanningGains(Device->AmbiCoeffs, Device->NumChannels, coeffs, Gain, DirGains); + for(i = 0;i < Device->NumChannels;i++) + { + if(Device->ChannelName[i] == LFE) + continue; + State->Early.PanGain[i&3][i] = lerp(AmbientGains[i], DirGains[i], length); + } + } - // The late delay lines are calculated from the lowest reverb density. - for(index = 0;index < 4;index++) + memset(State->Late.PanGain, 0, sizeof(State->Late.PanGain)); + length = sqrtf(LateReverbPan[0]*LateReverbPan[0] + LateReverbPan[1]*LateReverbPan[1] + LateReverbPan[2]*LateReverbPan[2]); + if(!(length > FLT_EPSILON)) { - length = LATE_LINE_LENGTH[index] * (1.0f + LATE_LINE_MULTIPLIER); - totalSamples += CalcLineLength(length, totalSamples, frequency, 0, - &State->Late.Delay[index]); + for(i = 0;i < Device->NumChannels;i++) + { + if(Device->ChannelName[i] == LFE) + continue; + State->Late.PanGain[i&3][i] = AmbientGains[i]; + } } + else + { + ALfloat pan[3] = { + LateReverbPan[0] / length, + LateReverbPan[1] / length, + -LateReverbPan[2] / length, + }; + length = minf(length, 1.0f); - // The echo all-pass and delay lines. - totalSamples += CalcLineLength(ECHO_ALLPASS_LENGTH, totalSamples, - frequency, 0, &State->Echo.ApDelay); - totalSamples += CalcLineLength(AL_EAXREVERB_MAX_ECHO_TIME, totalSamples, - frequency, 0, &State->Echo.Delay); + CalcDirectionCoeffs(pan, coeffs); + ComputePanningGains(Device->AmbiCoeffs, Device->NumChannels, coeffs, Gain, DirGains); + for(i = 0;i < Device->NumChannels;i++) + { + if(Device->ChannelName[i] == LFE) + continue; + State->Late.PanGain[i&3][i] = lerp(AmbientGains[i], DirGains[i], length); + } + } +} - if(totalSamples != State->TotalSamples) +static ALvoid Update3DPanning(const ALCdevice *Device, const ALfloat *ReflectionsPan, const ALfloat *LateReverbPan, ALfloat Gain, ALreverbState *State) +{ + static const ALfloat PanDirs[4][3] = { + { -0.707106781f, 0.0f, -0.707106781f }, /* Front left */ + { 0.707106781f, 0.0f, -0.707106781f }, /* Front right */ + { 0.707106781f, 0.0f, 0.707106781f }, /* Back right */ + { -0.707106781f, 0.0f, 0.707106781f } /* Back left */ + }; + ALfloat coeffs[MAX_AMBI_COEFFS]; + ALfloat gain[4]; + ALfloat length; + ALuint i; + + /* 0.5 would be the gain scaling when the panning vector is 0. This also + * equals sqrt(1/4), a nice gain scaling for the four virtual points + * producing an "ambient" response. + */ + gain[0] = gain[1] = gain[2] = gain[3] = 0.5f; + length = sqrtf(ReflectionsPan[0]*ReflectionsPan[0] + ReflectionsPan[1]*ReflectionsPan[1] + ReflectionsPan[2]*ReflectionsPan[2]); + if(length > 1.0f) { - TRACE("New reverb buffer length: %u samples (%f sec)\n", totalSamples, totalSamples/(float)frequency); - newBuffer = realloc(State->SampleBuffer, sizeof(ALfloat) * totalSamples); - if(newBuffer == NULL) - return AL_FALSE; - State->SampleBuffer = newBuffer; - State->TotalSamples = totalSamples; + ALfloat pan[3] = { + ReflectionsPan[0] / length, + ReflectionsPan[1] / length, + -ReflectionsPan[2] / length, + }; + for(i = 0;i < 4;i++) + { + ALfloat dotp = pan[0]*PanDirs[i][0] + pan[1]*PanDirs[i][1] + pan[2]*PanDirs[i][2]; + gain[i] = dotp*0.5f + 0.5f; + } } - - // Update all delays to reflect the new sample buffer. - RealizeLineOffset(State->SampleBuffer, &State->Delay); - RealizeLineOffset(State->SampleBuffer, &State->Decorrelator); - for(index = 0;index < 4;index++) + else if(length > FLT_EPSILON) { - RealizeLineOffset(State->SampleBuffer, &State->Early.Delay[index]); - RealizeLineOffset(State->SampleBuffer, &State->Late.ApDelay[index]); - RealizeLineOffset(State->SampleBuffer, &State->Late.Delay[index]); + for(i = 0;i < 4;i++) + { + ALfloat dotp = ReflectionsPan[0]*PanDirs[i][0] + ReflectionsPan[1]*PanDirs[i][1] + + -ReflectionsPan[2]*PanDirs[i][2]; + gain[i] = dotp*0.5f + 0.5f; + } + } + for(i = 0;i < 4;i++) + { + CalcDirectionCoeffs(PanDirs[i], coeffs); + ComputePanningGains(Device->AmbiCoeffs, Device->NumChannels, coeffs, Gain*gain[i], State->Early.PanGain[i]); } - RealizeLineOffset(State->SampleBuffer, &State->Mod.Delay); - RealizeLineOffset(State->SampleBuffer, &State->Echo.ApDelay); - RealizeLineOffset(State->SampleBuffer, &State->Echo.Delay); - - // Clear the sample buffer. - for(index = 0;index < State->TotalSamples;index++) - State->SampleBuffer[index] = 0.0f; - return AL_TRUE; + gain[0] = gain[1] = gain[2] = gain[3] = 0.5f; + length = sqrtf(LateReverbPan[0]*LateReverbPan[0] + LateReverbPan[1]*LateReverbPan[1] + LateReverbPan[2]*LateReverbPan[2]); + if(length > 1.0f) + { + ALfloat pan[3] = { + LateReverbPan[0] / length, + LateReverbPan[1] / length, + -LateReverbPan[2] / length, + }; + length = 1.0f; + for(i = 0;i < 4;i++) + { + ALfloat dotp = pan[0]*PanDirs[i][0] + pan[1]*PanDirs[i][1] + pan[2]*PanDirs[i][2]; + gain[i] = dotp*0.5f + 0.5f; + } + } + else if(length > FLT_EPSILON) + { + for(i = 0;i < 4;i++) + { + ALfloat dotp = LateReverbPan[0]*PanDirs[i][0] + LateReverbPan[1]*PanDirs[i][1] + + -LateReverbPan[2]*PanDirs[i][2]; + gain[i] = dotp*0.5f + 0.5f; + } + } + for(i = 0;i < 4;i++) + { + CalcDirectionCoeffs(PanDirs[i], coeffs); + ComputePanningGains(Device->AmbiCoeffs, Device->NumChannels, coeffs, Gain*gain[i], State->Late.PanGain[i]); + } } -static ALboolean ALreverbState_deviceUpdate(ALreverbState *State, ALCdevice *Device) +static ALvoid ALreverbState_update(ALreverbState *State, const ALCdevice *Device, const ALeffectslot *Slot) { - ALuint frequency = Device->Frequency, index; + const ALeffectProps *props = &Slot->EffectProps; + ALuint frequency = Device->Frequency; + ALfloat lfscale, hfscale, hfRatio; + ALfloat gain, gainlf, gainhf; + ALfloat cw, x, y; - // Allocate the delay lines. - if(!AllocLines(frequency, State)) - return AL_FALSE; + if(Slot->EffectType == AL_EFFECT_EAXREVERB && !EmulateEAXReverb) + State->IsEax = AL_TRUE; + else if(Slot->EffectType == AL_EFFECT_REVERB || EmulateEAXReverb) + State->IsEax = AL_FALSE; - // Calculate the modulation filter coefficient. Notice that the exponent - // is calculated given the current sample rate. This ensures that the - // resulting filter response over time is consistent across all sample - // rates. - State->Mod.Coeff = powf(MODULATION_FILTER_COEFF, - MODULATION_FILTER_CONST / frequency); + // Calculate the master filters + hfscale = props->Reverb.HFReference / frequency; + gainhf = maxf(props->Reverb.GainHF, 0.0001f); + ALfilterState_setParams(&State->LpFilter, ALfilterType_HighShelf, + gainhf, hfscale, calc_rcpQ_from_slope(gainhf, 0.75f)); + lfscale = props->Reverb.LFReference / frequency; + gainlf = maxf(props->Reverb.GainLF, 0.0001f); + ALfilterState_setParams(&State->HpFilter, ALfilterType_LowShelf, + gainlf, lfscale, calc_rcpQ_from_slope(gainlf, 0.75f)); - // The early reflection and late all-pass filter line lengths are static, - // so their offsets only need to be calculated once. - for(index = 0;index < 4;index++) - { - State->Early.Offset[index] = fastf2u(EARLY_LINE_LENGTH[index] * - frequency); - State->Late.ApOffset[index] = fastf2u(ALLPASS_LINE_LENGTH[index] * - frequency); - } + // Update the modulator line. + UpdateModulator(props->Reverb.ModulationTime, props->Reverb.ModulationDepth, + frequency, State); - // The echo all-pass filter line length is static, so its offset only - // needs to be calculated once. - State->Echo.ApOffset = fastf2u(ECHO_ALLPASS_LENGTH * frequency); + // Update the initial effect delay. + UpdateDelayLine(props->Reverb.ReflectionsDelay, props->Reverb.LateReverbDelay, + frequency, State); + + // Update the early lines. + UpdateEarlyLines(props->Reverb.ReflectionsGain, + props->Reverb.LateReverbDelay, State); + + // Update the decorrelator. + UpdateDecorrelator(props->Reverb.Density, frequency, State); + + // Get the mixing matrix coefficients (x and y). + CalcMatrixCoeffs(props->Reverb.Diffusion, &x, &y); + // Then divide x into y to simplify the matrix calculation. + State->Late.MixCoeff = y / x; + + // If the HF limit parameter is flagged, calculate an appropriate limit + // based on the air absorption parameter. + hfRatio = props->Reverb.DecayHFRatio; + if(props->Reverb.DecayHFLimit && props->Reverb.AirAbsorptionGainHF < 1.0f) + hfRatio = CalcLimitedHfRatio(hfRatio, props->Reverb.AirAbsorptionGainHF, + props->Reverb.DecayTime); + + cw = cosf(F_TAU * hfscale); + // Update the late lines. + UpdateLateLines(props->Reverb.LateReverbGain, x, + props->Reverb.Density, props->Reverb.DecayTime, + props->Reverb.Diffusion, props->Reverb.EchoDepth, + hfRatio, cw, frequency, State); + + // Update the echo line. + UpdateEchoLine(props->Reverb.LateReverbGain, + props->Reverb.EchoTime, props->Reverb.DecayTime, + props->Reverb.Diffusion, props->Reverb.EchoDepth, + hfRatio, cw, frequency, State); - return AL_TRUE; + gain = props->Reverb.Gain * Slot->Gain * ReverbBoost; + // Update early and late 3D panning. + if(Device->Hrtf || Device->FmtChans == DevFmtBFormat3D) + Update3DPanning(Device, props->Reverb.ReflectionsPan, + props->Reverb.LateReverbPan, gain, State); + else + UpdateDirectPanning(Device, props->Reverb.ReflectionsPan, + props->Reverb.LateReverbPan, gain, State); } -// Calculate a decay coefficient given the length of each cycle and the time -// until the decay reaches -60 dB. -static inline ALfloat CalcDecayCoeff(ALfloat length, ALfloat decayTime) -{ - return powf(0.001f/*-60 dB*/, length/decayTime); -} -// Calculate a decay length from a coefficient and the time until the decay -// reaches -60 dB. -static inline ALfloat CalcDecayLength(ALfloat coeff, ALfloat decayTime) +/************************************** + * Effect Processing * + **************************************/ + +// Basic delay line input/output routines. +static inline ALfloat DelayLineOut(DelayLine *Delay, ALuint offset) { - return log10f(coeff) * decayTime / log10f(0.001f)/*-60 dB*/; + return Delay->Line[offset&Delay->Mask]; } -// Calculate an attenuation to be applied to the input of any echo models to -// compensate for modal density and decay time. -static inline ALfloat CalcDensityGain(ALfloat a) +static inline ALvoid DelayLineIn(DelayLine *Delay, ALuint offset, ALfloat in) { - /* The energy of a signal can be obtained by finding the area under the - * squared signal. This takes the form of Sum(x_n^2), where x is the - * amplitude for the sample n. - * - * Decaying feedback matches exponential decay of the form Sum(a^n), - * where a is the attenuation coefficient, and n is the sample. The area - * under this decay curve can be calculated as: 1 / (1 - a). - * - * Modifying the above equation to find the squared area under the curve - * (for energy) yields: 1 / (1 - a^2). Input attenuation can then be - * calculated by inverting the square root of this approximation, - * yielding: 1 / sqrt(1 / (1 - a^2)), simplified to: sqrt(1 - a^2). - */ - return sqrtf(1.0f - (a * a)); + Delay->Line[offset&Delay->Mask] = in; } -// Calculate the mixing matrix coefficients given a diffusion factor. -static inline ALvoid CalcMatrixCoeffs(ALfloat diffusion, ALfloat *x, ALfloat *y) +// Given an input sample, this function produces modulation for the late +// reverb. +static inline ALfloat EAXModulation(ALreverbState *State, ALuint offset, ALfloat in) { - ALfloat n, t; + ALfloat sinus, frac, fdelay; + ALfloat out0, out1; + ALuint delay; - // The matrix is of order 4, so n is sqrt (4 - 1). - n = sqrtf(3.0f); - t = diffusion * atanf(n); + // Calculate the sinus rythm (dependent on modulation time and the + // sampling rate). The center of the sinus is moved to reduce the delay + // of the effect when the time or depth are low. + sinus = 1.0f - cosf(F_TAU * State->Mod.Index / State->Mod.Range); - // Calculate the first mixing matrix coefficient. - *x = cosf(t); - // Calculate the second mixing matrix coefficient. - *y = sinf(t) / n; -} + // Step the modulation index forward, keeping it bound to its range. + State->Mod.Index = (State->Mod.Index + 1) % State->Mod.Range; -// Calculate the limited HF ratio for use with the late reverb low-pass -// filters. -static ALfloat CalcLimitedHfRatio(ALfloat hfRatio, ALfloat airAbsorptionGainHF, ALfloat decayTime) -{ - ALfloat limitRatio; + // The depth determines the range over which to read the input samples + // from, so it must be filtered to reduce the distortion caused by even + // small parameter changes. + State->Mod.Filter = lerp(State->Mod.Filter, State->Mod.Depth, + State->Mod.Coeff); - /* 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 / (CalcDecayLength(airAbsorptionGainHF, decayTime) * - SPEEDOFSOUNDMETRESPERSEC); - /* Using the limit calculated above, apply the upper bound to the HF - * ratio. Also need to limit the result to a minimum of 0.1, just like the - * HF ratio parameter. */ - return clampf(limitRatio, 0.1f, hfRatio); + // Calculate the read offset and fraction between it and the next sample. + frac = modff(State->Mod.Filter*sinus + 1.0f, &fdelay); + delay = fastf2u(fdelay); + + // Get the two samples crossed by the offset, and feed the delay line + // with the next input sample. + out0 = DelayLineOut(&State->Mod.Delay, offset - delay); + out1 = DelayLineOut(&State->Mod.Delay, offset - delay - 1); + DelayLineIn(&State->Mod.Delay, offset, in); + + // The output is obtained by linearly interpolating the two samples that + // were acquired above. + return lerp(out0, out1, frac); } -// Calculate the coefficient for a HF (and eventually LF) decay damping -// filter. -static inline ALfloat CalcDampingCoeff(ALfloat hfRatio, ALfloat length, ALfloat decayTime, ALfloat decayCoeff, ALfloat cw) +// Given some input sample, this function produces four-channel outputs for the +// early reflections. +static inline ALvoid EarlyReflection(ALreverbState *State, ALuint todo, ALfloat (*restrict out)[4]) { - ALfloat coeff, g; + ALfloat d[4], v, f[4]; + ALuint i; - // Eventually this should boost the high frequencies when the ratio - // exceeds 1. - coeff = 0.0f; - if (hfRatio < 1.0f) + for(i = 0;i < todo;i++) { - // Calculate the low-pass coefficient by dividing the HF decay - // coefficient by the full decay coefficient. - g = CalcDecayCoeff(length, decayTime * hfRatio) / decayCoeff; - - // Damping is done with a 1-pole filter, so g needs to be squared. - g *= g; - if(g < 0.9999f) /* 1-epsilon */ - { - /* Be careful with gains < 0.001, as that causes the coefficient - * head towards 1, which will flatten the signal. */ - g = maxf(g, 0.001f); - coeff = (1 - g*cw - sqrtf(2*g*(1-cw) - g*g*(1 - cw*cw))) / - (1 - g); - } + ALuint offset = State->Offset+i; - // Very low decay times will produce minimal output, so apply an - // upper bound to the coefficient. - coeff = minf(coeff, 0.98f); - } - return coeff; -} + // Obtain the decayed results of each early delay line. + d[0] = DelayLineOut(&State->Early.Delay[0], offset-State->Early.Offset[0]) * State->Early.Coeff[0]; + d[1] = DelayLineOut(&State->Early.Delay[1], offset-State->Early.Offset[1]) * State->Early.Coeff[1]; + d[2] = DelayLineOut(&State->Early.Delay[2], offset-State->Early.Offset[2]) * State->Early.Coeff[2]; + d[3] = DelayLineOut(&State->Early.Delay[3], offset-State->Early.Offset[3]) * State->Early.Coeff[3]; -// Update the EAX modulation index, range, and depth. Keep in mind that this -// kind of vibrato is additive and not multiplicative as one may expect. The -// downswing will sound stronger than the upswing. -static ALvoid UpdateModulator(ALfloat modTime, ALfloat modDepth, ALuint frequency, ALreverbState *State) -{ - ALuint range; + /* 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 feed-back delay network (FDN). + * N + * --- + * \ + * v = 2/N / d_i + * --- + * i=1 + */ + v = (d[0] + d[1] + d[2] + d[3]) * 0.5f; + // The junction is loaded with the input here. + v += DelayLineOut(&State->Delay, offset-State->DelayTap[0]); - /* Modulation is calculated in two parts. - * - * The modulation time effects the sinus applied to the change in - * frequency. An index out of the current time range (both in samples) - * is incremented each sample. The range is bound to a reasonable - * minimum (1 sample) and when the timing changes, the index is rescaled - * to the new range (to keep the sinus consistent). - */ - range = maxu(fastf2u(modTime*frequency), 1); - State->Mod.Index = (ALuint)(State->Mod.Index * (ALuint64)range / - State->Mod.Range); - State->Mod.Range = range; + // 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]; - /* The modulation depth effects the amount of frequency change over the - * range of the sinus. It needs to be scaled by the modulation time so - * that a given depth produces a consistent change in frequency over all - * ranges of time. Since the depth is applied to a sinus value, it needs - * to be halfed once for the sinus range and again for the sinus swing - * in time (half of it is spent decreasing the frequency, half is spent - * increasing it). - */ - State->Mod.Depth = modDepth * MODULATION_DEPTH_COEFF * modTime / 2.0f / - 2.0f * frequency; -} + // Re-feed the delay lines. + DelayLineIn(&State->Early.Delay[0], offset, f[0]); + DelayLineIn(&State->Early.Delay[1], offset, f[1]); + DelayLineIn(&State->Early.Delay[2], offset, f[2]); + DelayLineIn(&State->Early.Delay[3], offset, f[3]); -// Update the offsets for the initial effect delay line. -static ALvoid UpdateDelayLine(ALfloat earlyDelay, ALfloat lateDelay, ALuint frequency, ALreverbState *State) -{ - // Calculate the initial delay taps. - State->DelayTap[0] = fastf2u(earlyDelay * frequency); - State->DelayTap[1] = fastf2u((earlyDelay + lateDelay) * frequency); + // Output the results of the junction for all four channels. + out[i][0] = State->Early.Gain * f[0]; + out[i][1] = State->Early.Gain * f[1]; + out[i][2] = State->Early.Gain * f[2]; + out[i][3] = State->Early.Gain * f[3]; + } } -// Update the early reflections gain and line coefficients. -static ALvoid UpdateEarlyLines(ALfloat earlyGain, ALfloat lateDelay, ALreverbState *State) +// Basic attenuated all-pass input/output routine. +static inline ALfloat AllpassInOut(DelayLine *Delay, ALuint outOffset, ALuint inOffset, ALfloat in, ALfloat feedCoeff, ALfloat coeff) { - ALuint index; + ALfloat out, feed; - // Calculate the early reflections gain (from the master effect gain, and - // reflections gain parameters) with a constant attenuation of 0.5. - State->Early.Gain = 0.5f * earlyGain; + out = DelayLineOut(Delay, outOffset); + feed = feedCoeff * in; + DelayLineIn(Delay, inOffset, (feedCoeff * (out - feed)) + in); - // Calculate the gain (coefficient) for each early delay line using the - // late delay time. This expands the early reflections to the start of - // the late reverb. - for(index = 0;index < 4;index++) - State->Early.Coeff[index] = CalcDecayCoeff(EARLY_LINE_LENGTH[index], - lateDelay); + // The time-based attenuation is only applied to the delay output to + // keep it from affecting the feed-back path (which is already controlled + // by the all-pass feed coefficient). + return (coeff * out) - feed; } -// Update the offsets for the decorrelator line. -static ALvoid UpdateDecorrelator(ALfloat density, ALuint frequency, ALreverbState *State) +// All-pass input/output routine for late reverb. +static inline ALfloat LateAllPassInOut(ALreverbState *State, ALuint offset, ALuint index, ALfloat in) { - ALuint index; - ALfloat length; + return AllpassInOut(&State->Late.ApDelay[index], + offset - State->Late.ApOffset[index], + offset, in, State->Late.ApFeedCoeff, + State->Late.ApCoeff[index]); +} - /* The late reverb inputs are decorrelated to smooth the reverb tail and - * reduce harsh echos. The first tap occurs immediately, while the - * remaining taps are delayed by multiples of a fraction of the smallest - * cyclical delay time. - * - * offset[index] = (FRACTION (MULTIPLIER^index)) smallest_delay - */ - for(index = 0;index < 3;index++) - { - length = (DECO_FRACTION * powf(DECO_MULTIPLIER, (ALfloat)index)) * - LATE_LINE_LENGTH[0] * (1.0f + (density * LATE_LINE_MULTIPLIER)); - State->DecoTap[index] = fastf2u(length * frequency); - } +// Low-pass filter input/output routine for late reverb. +static inline ALfloat LateLowPassInOut(ALreverbState *State, ALuint index, ALfloat in) +{ + in = lerp(in, State->Late.LpSample[index], State->Late.LpCoeff[index]); + State->Late.LpSample[index] = in; + return in; } -// Update the late reverb gains, line lengths, and line coefficients. -static ALvoid UpdateLateLines(ALfloat lateGain, ALfloat xMix, ALfloat density, ALfloat decayTime, ALfloat diffusion, ALfloat echoDepth, ALfloat hfRatio, ALfloat cw, ALuint frequency, ALreverbState *State) +// Given four decorrelated input samples, this function produces four-channel +// output for the late reverb. +static inline ALvoid LateReverb(ALreverbState *State, ALuint todo, ALfloat (*restrict out)[4]) { - ALfloat length; - ALuint index; - - /* Calculate the late reverb gain (from the master effect gain, and late - * reverb gain parameters). Since the output is tapped prior to the - * application of the next delay line coefficients, this gain needs to be - * attenuated by the 'x' mixing matrix coefficient as well. Also attenuate - * the late reverb when echo depth is high and diffusion is low, so the - * echo is slightly stronger than the decorrelated echos in the reverb - * tail. - */ - State->Late.Gain = lateGain * xMix * (1.0f - (echoDepth*0.5f*(1.0f - diffusion))); + ALfloat d[4], f[4]; + ALuint i; - /* To compensate for changes in modal density and decay time of the late - * reverb signal, the input is attenuated based on the maximal energy of - * the outgoing signal. This approximation is used to keep the apparent - * energy of the signal equal for all ranges of density and decay time. - * - * The average length of the cyclcical delay lines is used to calculate - * the attenuation coefficient. - */ - length = (LATE_LINE_LENGTH[0] + LATE_LINE_LENGTH[1] + - LATE_LINE_LENGTH[2] + LATE_LINE_LENGTH[3]) / 4.0f; - length *= 1.0f + (density * LATE_LINE_MULTIPLIER); - State->Late.DensityGain = CalcDensityGain( - CalcDecayCoeff(length, decayTime) - ); + for(i = 0;i < todo;i++) + { + ALuint offset = State->Offset+i; - // Calculate the all-pass feed-back and feed-forward coefficient. - State->Late.ApFeedCoeff = 0.5f * powf(diffusion, 2.0f); + f[0] = DelayLineOut(&State->Decorrelator, offset); + f[1] = DelayLineOut(&State->Decorrelator, offset-State->DecoTap[0]); + f[2] = DelayLineOut(&State->Decorrelator, offset-State->DecoTap[1]); + f[3] = DelayLineOut(&State->Decorrelator, offset-State->DecoTap[2]); - for(index = 0;index < 4;index++) - { - // Calculate the gain (coefficient) for each all-pass line. - State->Late.ApCoeff[index] = CalcDecayCoeff( - ALLPASS_LINE_LENGTH[index], decayTime - ); + // Obtain the decayed results of the cyclical delay lines, and add the + // corresponding input channels. Then pass the results through the + // low-pass filters. + f[0] += DelayLineOut(&State->Late.Delay[0], offset-State->Late.Offset[0]) * State->Late.Coeff[0]; + f[1] += DelayLineOut(&State->Late.Delay[1], offset-State->Late.Offset[1]) * State->Late.Coeff[1]; + f[2] += DelayLineOut(&State->Late.Delay[2], offset-State->Late.Offset[2]) * State->Late.Coeff[2]; + f[3] += DelayLineOut(&State->Late.Delay[3], offset-State->Late.Offset[3]) * State->Late.Coeff[3]; - // Calculate the length (in seconds) of each cyclical delay line. - length = LATE_LINE_LENGTH[index] * - (1.0f + (density * LATE_LINE_MULTIPLIER)); + // This is where the feed-back cycles from line 0 to 1 to 3 to 2 and + // back to 0. + d[0] = LateLowPassInOut(State, 2, f[2]); + d[1] = LateLowPassInOut(State, 0, f[0]); + d[2] = LateLowPassInOut(State, 3, f[3]); + d[3] = LateLowPassInOut(State, 1, f[1]); - // Calculate the delay offset for each cyclical delay line. - State->Late.Offset[index] = fastf2u(length * frequency); + // To help increase diffusion, run each line through an all-pass filter. + // When there is no diffusion, the shortest all-pass filter will feed + // the shortest delay line. + d[0] = LateAllPassInOut(State, offset, 0, d[0]); + d[1] = LateAllPassInOut(State, offset, 1, d[1]); + d[2] = LateAllPassInOut(State, offset, 2, d[2]); + d[3] = LateAllPassInOut(State, offset, 3, d[3]); - // Calculate the gain (coefficient) for each cyclical line. - State->Late.Coeff[index] = CalcDecayCoeff(length, decayTime); + /* Late reverb is done with a modified feed-back delay network (FDN) + * topology. Four input lines are each fed through their own all-pass + * filter and then into the mixing matrix. The four outputs of the + * mixing matrix are then cycled back to the inputs. Each output feeds + * a different input to form a circlular feed cycle. + * + * The mixing matrix used is a 4D skew-symmetric rotation matrix + * derived using a single unitary rotational parameter: + * + * [ d, a, b, c ] 1 = a^2 + b^2 + c^2 + d^2 + * [ -a, d, c, -b ] + * [ -b, -c, d, a ] + * [ -c, b, -a, d ] + * + * The rotation is constructed from the effect's diffusion parameter, + * yielding: 1 = x^2 + 3 y^2; where a, b, and c are the coefficient y + * with differing signs, and d is the coefficient x. The matrix is + * thus: + * + * [ x, y, -y, y ] n = sqrt(matrix_order - 1) + * [ -y, x, y, y ] t = diffusion_parameter * atan(n) + * [ y, -y, x, y ] x = cos(t) + * [ -y, -y, -y, x ] y = sin(t) / n + * + * To reduce the number of multiplies, the x coefficient is applied + * with the cyclical delay line coefficients. Thus only the y + * coefficient is applied when mixing, and is modified to be: y / x. + */ + f[0] = d[0] + (State->Late.MixCoeff * ( d[1] + -d[2] + d[3])); + f[1] = d[1] + (State->Late.MixCoeff * (-d[0] + d[2] + d[3])); + f[2] = d[2] + (State->Late.MixCoeff * ( d[0] + -d[1] + d[3])); + f[3] = d[3] + (State->Late.MixCoeff * (-d[0] + -d[1] + -d[2] )); - // Calculate the damping coefficient for each low-pass filter. - State->Late.LpCoeff[index] = CalcDampingCoeff( - hfRatio, length, decayTime, State->Late.Coeff[index], cw - ); + // Output the results of the matrix for all four channels, attenuated by + // the late reverb gain (which is attenuated by the 'x' mix coefficient). + // Mix early reflections and late reverb. + out[i][0] = State->Late.Gain * f[0]; + out[i][1] = State->Late.Gain * f[1]; + out[i][2] = State->Late.Gain * f[2]; + out[i][3] = State->Late.Gain * f[3]; - // Attenuate the cyclical line coefficients by the mixing coefficient - // (x). - State->Late.Coeff[index] *= xMix; + // Re-feed the cyclical delay lines. + DelayLineIn(&State->Late.Delay[0], offset, f[0]); + DelayLineIn(&State->Late.Delay[1], offset, f[1]); + DelayLineIn(&State->Late.Delay[2], offset, f[2]); + DelayLineIn(&State->Late.Delay[3], offset, f[3]); } } -// Update the echo gain, line offset, line coefficients, and mixing -// coefficients. -static ALvoid UpdateEchoLine(ALfloat lateGain, ALfloat echoTime, ALfloat decayTime, ALfloat diffusion, ALfloat echoDepth, ALfloat hfRatio, ALfloat cw, ALuint frequency, ALreverbState *State) +// Given an input sample, this function mixes echo into the four-channel late +// reverb. +static inline ALvoid EAXEcho(ALreverbState *State, ALuint todo, ALfloat (*restrict late)[4]) { - // Update the offset and coefficient for the echo delay line. - State->Echo.Offset = fastf2u(echoTime * frequency); + ALfloat out, feed; + ALuint i; - // Calculate the decay coefficient for the echo line. - State->Echo.Coeff = CalcDecayCoeff(echoTime, decayTime); + for(i = 0;i < todo;i++) + { + ALuint offset = State->Offset+i; - // Calculate the energy-based attenuation coefficient for the echo delay - // line. - State->Echo.DensityGain = CalcDensityGain(State->Echo.Coeff); + // Get the latest attenuated echo sample for output. + feed = DelayLineOut(&State->Echo.Delay, offset-State->Echo.Offset) * + State->Echo.Coeff; - // Calculate the echo all-pass feed coefficient. - State->Echo.ApFeedCoeff = 0.5f * powf(diffusion, 2.0f); + // Mix the output into the late reverb channels. + out = State->Echo.MixCoeff * feed; + late[i][0] += out; + late[i][1] += out; + late[i][2] += out; + late[i][3] += out; - // Calculate the echo all-pass attenuation coefficient. - State->Echo.ApCoeff = CalcDecayCoeff(ECHO_ALLPASS_LENGTH, decayTime); + // Mix the energy-attenuated input with the output and pass it through + // the echo low-pass filter. + feed += DelayLineOut(&State->Delay, offset-State->DelayTap[1]) * + State->Echo.DensityGain; + feed = lerp(feed, State->Echo.LpSample, State->Echo.LpCoeff); + State->Echo.LpSample = feed; - // Calculate the damping coefficient for each low-pass filter. - State->Echo.LpCoeff = CalcDampingCoeff(hfRatio, echoTime, decayTime, - State->Echo.Coeff, cw); + // Then the echo all-pass filter. + feed = AllpassInOut(&State->Echo.ApDelay, offset-State->Echo.ApOffset, + offset, feed, State->Echo.ApFeedCoeff, + State->Echo.ApCoeff); - /* Calculate the echo mixing coefficient. This is applied to the output mix - * only, not the feedback. - */ - State->Echo.MixCoeff = lateGain * echoDepth; + // Feed the delay with the mixed and filtered sample. + DelayLineIn(&State->Echo.Delay, offset, feed); + } } -// Update the early and late 3D panning gains. -static ALvoid UpdateDirectPanning(const ALCdevice *Device, const ALfloat *ReflectionsPan, const ALfloat *LateReverbPan, ALfloat Gain, ALreverbState *State) +// Perform the non-EAX reverb pass on a given input sample, resulting in +// four-channel output. +static inline ALvoid VerbPass(ALreverbState *State, ALuint todo, const ALfloat *in, ALfloat (*restrict early)[4], ALfloat (*restrict late)[4]) { - ALfloat AmbientGains[MAX_OUTPUT_CHANNELS]; - ALfloat DirGains[MAX_OUTPUT_CHANNELS]; - ALfloat coeffs[MAX_AMBI_COEFFS]; - ALfloat length; ALuint i; - ComputeAmbientGains(Device->AmbiCoeffs, Device->NumChannels, Gain, AmbientGains); - - memset(State->Early.PanGain, 0, sizeof(State->Early.PanGain)); - length = sqrtf(ReflectionsPan[0]*ReflectionsPan[0] + ReflectionsPan[1]*ReflectionsPan[1] + ReflectionsPan[2]*ReflectionsPan[2]); - if(!(length > FLT_EPSILON)) - { - for(i = 0;i < Device->NumChannels;i++) - { - if(Device->ChannelName[i] == LFE) - continue; - State->Early.PanGain[i&3][i] = AmbientGains[i]; - } - } - else - { - /* Note that EAX Reverb's panning vectors are using right-handed - * coordinates, rather that the OpenAL's left-handed coordinates. - * Negate Z to fix this. - */ - ALfloat pan[3] = { - ReflectionsPan[0] / length, - ReflectionsPan[1] / length, - -ReflectionsPan[2] / length, - }; - length = minf(length, 1.0f); - - CalcDirectionCoeffs(pan, coeffs); - ComputePanningGains(Device->AmbiCoeffs, Device->NumChannels, coeffs, Gain, DirGains); - for(i = 0;i < Device->NumChannels;i++) - { - if(Device->ChannelName[i] == LFE) - continue; - State->Early.PanGain[i&3][i] = lerp(AmbientGains[i], DirGains[i], length); - } - } - - memset(State->Late.PanGain, 0, sizeof(State->Late.PanGain)); - length = sqrtf(LateReverbPan[0]*LateReverbPan[0] + LateReverbPan[1]*LateReverbPan[1] + LateReverbPan[2]*LateReverbPan[2]); - if(!(length > FLT_EPSILON)) - { - for(i = 0;i < Device->NumChannels;i++) - { - if(Device->ChannelName[i] == LFE) - continue; - State->Late.PanGain[i&3][i] = AmbientGains[i]; - } - } - else - { - ALfloat pan[3] = { - LateReverbPan[0] / length, - LateReverbPan[1] / length, - -LateReverbPan[2] / length, - }; - length = minf(length, 1.0f); - - CalcDirectionCoeffs(pan, coeffs); - ComputePanningGains(Device->AmbiCoeffs, Device->NumChannels, coeffs, Gain, DirGains); - for(i = 0;i < Device->NumChannels;i++) - { - if(Device->ChannelName[i] == LFE) - continue; - State->Late.PanGain[i&3][i] = lerp(AmbientGains[i], DirGains[i], length); - } + // Low-pass filter the incoming samples. + for(i = 0;i < todo;i++) + DelayLineIn(&State->Delay, State->Offset+i, + ALfilterState_processSingle(&State->LpFilter, in[i]) + ); + + // Calculate the early reflection from the first delay tap. + EarlyReflection(State, todo, early); + + // Feed the decorrelator from the energy-attenuated output of the second + // delay tap. + for(i = 0;i < todo;i++) + { + ALuint offset = State->Offset+i; + ALfloat sample = DelayLineOut(&State->Delay, offset - State->DelayTap[1]) * + State->Late.DensityGain; + DelayLineIn(&State->Decorrelator, offset, sample); } + + // Calculate the late reverb from the decorrelator taps. + LateReverb(State, todo, late); + + // Step all delays forward one sample. + State->Offset += todo; } -static ALvoid Update3DPanning(const ALCdevice *Device, const ALfloat *ReflectionsPan, const ALfloat *LateReverbPan, ALfloat Gain, ALreverbState *State) +// Perform the EAX reverb pass on a given input sample, resulting in four- +// channel output. +static inline ALvoid EAXVerbPass(ALreverbState *State, ALuint todo, const ALfloat *input, ALfloat (*restrict early)[4], ALfloat (*restrict late)[4]) { - static const ALfloat PanDirs[4][3] = { - { -0.707106781f, 0.0f, -0.707106781f }, /* Front left */ - { 0.707106781f, 0.0f, -0.707106781f }, /* Front right */ - { 0.707106781f, 0.0f, 0.707106781f }, /* Back right */ - { -0.707106781f, 0.0f, 0.707106781f } /* Back left */ - }; - ALfloat coeffs[MAX_AMBI_COEFFS]; - ALfloat gain[4]; - ALfloat length; ALuint i; - /* 0.5 would be the gain scaling when the panning vector is 0. This also - * equals sqrt(1/4), a nice gain scaling for the four virtual points - * producing an "ambient" response. - */ - gain[0] = gain[1] = gain[2] = gain[3] = 0.5f; - length = sqrtf(ReflectionsPan[0]*ReflectionsPan[0] + ReflectionsPan[1]*ReflectionsPan[1] + ReflectionsPan[2]*ReflectionsPan[2]); - if(length > 1.0f) - { - ALfloat pan[3] = { - ReflectionsPan[0] / length, - ReflectionsPan[1] / length, - -ReflectionsPan[2] / length, - }; - for(i = 0;i < 4;i++) - { - ALfloat dotp = pan[0]*PanDirs[i][0] + pan[1]*PanDirs[i][1] + pan[2]*PanDirs[i][2]; - gain[i] = dotp*0.5f + 0.5f; - } - } - else if(length > FLT_EPSILON) - { - for(i = 0;i < 4;i++) - { - ALfloat dotp = ReflectionsPan[0]*PanDirs[i][0] + ReflectionsPan[1]*PanDirs[i][1] + - -ReflectionsPan[2]*PanDirs[i][2]; - gain[i] = dotp*0.5f + 0.5f; - } - } - for(i = 0;i < 4;i++) + // Band-pass and modulate the incoming samples. + for(i = 0;i < todo;i++) { - CalcDirectionCoeffs(PanDirs[i], coeffs); - ComputePanningGains(Device->AmbiCoeffs, Device->NumChannels, coeffs, Gain*gain[i], State->Early.PanGain[i]); - } + ALfloat sample = input[i]; + sample = ALfilterState_processSingle(&State->LpFilter, sample); + sample = ALfilterState_processSingle(&State->HpFilter, sample); - gain[0] = gain[1] = gain[2] = gain[3] = 0.5f; - length = sqrtf(LateReverbPan[0]*LateReverbPan[0] + LateReverbPan[1]*LateReverbPan[1] + LateReverbPan[2]*LateReverbPan[2]); - if(length > 1.0f) - { - ALfloat pan[3] = { - LateReverbPan[0] / length, - LateReverbPan[1] / length, - -LateReverbPan[2] / length, - }; - length = 1.0f; - for(i = 0;i < 4;i++) - { - ALfloat dotp = pan[0]*PanDirs[i][0] + pan[1]*PanDirs[i][1] + pan[2]*PanDirs[i][2]; - gain[i] = dotp*0.5f + 0.5f; - } - } - else if(length > FLT_EPSILON) - { - for(i = 0;i < 4;i++) - { - ALfloat dotp = LateReverbPan[0]*PanDirs[i][0] + LateReverbPan[1]*PanDirs[i][1] + - -LateReverbPan[2]*PanDirs[i][2]; - gain[i] = dotp*0.5f + 0.5f; - } + // Perform any modulation on the input. + sample = EAXModulation(State, State->Offset+i, sample); + + // Feed the initial delay line. + DelayLineIn(&State->Delay, State->Offset+i, sample); } - for(i = 0;i < 4;i++) + + // Calculate the early reflection from the first delay tap. + EarlyReflection(State, todo, early); + + // Feed the decorrelator from the energy-attenuated output of the second + // delay tap. + for(i = 0;i < todo;i++) { - CalcDirectionCoeffs(PanDirs[i], coeffs); - ComputePanningGains(Device->AmbiCoeffs, Device->NumChannels, coeffs, Gain*gain[i], State->Late.PanGain[i]); + ALuint offset = State->Offset+i; + ALfloat sample = DelayLineOut(&State->Delay, offset - State->DelayTap[1]) * + State->Late.DensityGain; + DelayLineIn(&State->Decorrelator, offset, sample); } -} -static ALvoid ALreverbState_update(ALreverbState *State, const ALCdevice *Device, const ALeffectslot *Slot) -{ - const ALeffectProps *props = &Slot->EffectProps; - ALuint frequency = Device->Frequency; - ALfloat lfscale, hfscale, hfRatio; - ALfloat gain, gainlf, gainhf; - ALfloat cw, x, y; + // Calculate the late reverb from the decorrelator taps. + LateReverb(State, todo, late); - if(Slot->EffectType == AL_EFFECT_EAXREVERB && !EmulateEAXReverb) - State->IsEax = AL_TRUE; - else if(Slot->EffectType == AL_EFFECT_REVERB || EmulateEAXReverb) - State->IsEax = AL_FALSE; + // Calculate and mix in any echo. + EAXEcho(State, todo, late); - // Calculate the master filters - hfscale = props->Reverb.HFReference / frequency; - gainhf = maxf(props->Reverb.GainHF, 0.0001f); - ALfilterState_setParams(&State->LpFilter, ALfilterType_HighShelf, - gainhf, hfscale, calc_rcpQ_from_slope(gainhf, 0.75f)); - lfscale = props->Reverb.LFReference / frequency; - gainlf = maxf(props->Reverb.GainLF, 0.0001f); - ALfilterState_setParams(&State->HpFilter, ALfilterType_LowShelf, - gainlf, lfscale, calc_rcpQ_from_slope(gainlf, 0.75f)); + // Step all delays forward. + State->Offset += todo; +} - // Update the modulator line. - UpdateModulator(props->Reverb.ModulationTime, props->Reverb.ModulationDepth, - frequency, State); +static ALvoid ALreverbState_processStandard(ALreverbState *State, ALuint SamplesToDo, const ALfloat *restrict SamplesIn, ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALuint NumChannels) +{ + ALfloat (*restrict early)[4] = State->EarlySamples; + ALfloat (*restrict late)[4] = State->ReverbSamples; + ALuint index, c, i, l; + ALfloat gain; - // Update the initial effect delay. - UpdateDelayLine(props->Reverb.ReflectionsDelay, props->Reverb.LateReverbDelay, - frequency, State); + /* Process reverb for these samples. */ + for(index = 0;index < SamplesToDo;) + { + ALuint todo = minu(SamplesToDo-index, MAX_UPDATE_SAMPLES); - // Update the early lines. - UpdateEarlyLines(props->Reverb.ReflectionsGain, - props->Reverb.LateReverbDelay, State); + VerbPass(State, todo, &SamplesIn[index], early, late); - // Update the decorrelator. - UpdateDecorrelator(props->Reverb.Density, frequency, State); + for(l = 0;l < 4;l++) + { + for(c = 0;c < NumChannels;c++) + { + gain = State->Early.PanGain[l][c]; + if(fabsf(gain) > GAIN_SILENCE_THRESHOLD) + { + for(i = 0;i < todo;i++) + SamplesOut[c][index+i] += gain*early[i][l]; + } + gain = State->Late.PanGain[l][c]; + if(fabsf(gain) > GAIN_SILENCE_THRESHOLD) + { + for(i = 0;i < todo;i++) + SamplesOut[c][index+i] += gain*late[i][l]; + } + } + } - // Get the mixing matrix coefficients (x and y). - CalcMatrixCoeffs(props->Reverb.Diffusion, &x, &y); - // Then divide x into y to simplify the matrix calculation. - State->Late.MixCoeff = y / x; + index += todo; + } +} - // If the HF limit parameter is flagged, calculate an appropriate limit - // based on the air absorption parameter. - hfRatio = props->Reverb.DecayHFRatio; - if(props->Reverb.DecayHFLimit && props->Reverb.AirAbsorptionGainHF < 1.0f) - hfRatio = CalcLimitedHfRatio(hfRatio, props->Reverb.AirAbsorptionGainHF, - props->Reverb.DecayTime); +static ALvoid ALreverbState_processEax(ALreverbState *State, ALuint SamplesToDo, const ALfloat *restrict SamplesIn, ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALuint NumChannels) +{ + ALfloat (*restrict early)[4] = State->EarlySamples; + ALfloat (*restrict late)[4] = State->ReverbSamples; + ALuint index, c, i, l; + ALfloat gain; - cw = cosf(F_TAU * hfscale); - // Update the late lines. - UpdateLateLines(props->Reverb.LateReverbGain, x, - props->Reverb.Density, props->Reverb.DecayTime, - props->Reverb.Diffusion, props->Reverb.EchoDepth, - hfRatio, cw, frequency, State); + /* Process reverb for these samples. */ + for(index = 0;index < SamplesToDo;) + { + ALuint todo = minu(SamplesToDo-index, MAX_UPDATE_SAMPLES); - // Update the echo line. - UpdateEchoLine(props->Reverb.LateReverbGain, - props->Reverb.EchoTime, props->Reverb.DecayTime, - props->Reverb.Diffusion, props->Reverb.EchoDepth, - hfRatio, cw, frequency, State); + EAXVerbPass(State, todo, &SamplesIn[index], early, late); - gain = props->Reverb.Gain * Slot->Gain * ReverbBoost; - // Update early and late 3D panning. - if(Device->Hrtf || Device->FmtChans == DevFmtBFormat3D) - Update3DPanning(Device, props->Reverb.ReflectionsPan, - props->Reverb.LateReverbPan, gain, State); - else - UpdateDirectPanning(Device, props->Reverb.ReflectionsPan, - props->Reverb.LateReverbPan, gain, State); -} + for(l = 0;l < 4;l++) + { + for(c = 0;c < NumChannels;c++) + { + gain = State->Early.PanGain[l][c]; + if(fabsf(gain) > GAIN_SILENCE_THRESHOLD) + { + for(i = 0;i < todo;i++) + SamplesOut[c][index+i] += gain*early[i][l]; + } + gain = State->Late.PanGain[l][c]; + if(fabsf(gain) > GAIN_SILENCE_THRESHOLD) + { + for(i = 0;i < todo;i++) + SamplesOut[c][index+i] += gain*late[i][l]; + } + } + } + index += todo; + } +} -static ALvoid ALreverbState_Destruct(ALreverbState *State) +static ALvoid ALreverbState_process(ALreverbState *State, ALuint SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALuint NumChannels) { - free(State->SampleBuffer); - State->SampleBuffer = NULL; + if(State->IsEax) + ALreverbState_processEax(State, SamplesToDo, SamplesIn[0], SamplesOut, NumChannels); + else + ALreverbState_processStandard(State, SamplesToDo, SamplesIn[0], SamplesOut, NumChannels); } -DECLARE_DEFAULT_ALLOCATORS(ALreverbState) - -DEFINE_ALEFFECTSTATE_VTABLE(ALreverbState); - typedef struct ALreverbStateFactory { DERIVE_FROM_TYPE(ALeffectStateFactory); -- cgit v1.2.3