rlm@0: /** rlm@0: * Reverb for the OpenAL cross platform audio library rlm@0: * Copyright (C) 2008-2009 by Christopher Fitzgerald. rlm@0: * This library is free software; you can redistribute it and/or rlm@0: * modify it under the terms of the GNU Library General Public rlm@0: * License as published by the Free Software Foundation; either rlm@0: * version 2 of the License, or (at your option) any later version. rlm@0: * rlm@0: * This library is distributed in the hope that it will be useful, rlm@0: * but WITHOUT ANY WARRANTY; without even the implied warranty of rlm@0: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU rlm@0: * Library General Public License for more details. rlm@0: * rlm@0: * You should have received a copy of the GNU Library General Public rlm@0: * License along with this library; if not, write to the rlm@0: * Free Software Foundation, Inc., 59 Temple Place - Suite 330, rlm@0: * Boston, MA 02111-1307, USA. rlm@0: * Or go to http://www.gnu.org/copyleft/lgpl.html rlm@0: */ rlm@0: rlm@0: #include "config.h" rlm@0: rlm@0: #include rlm@0: #include rlm@0: #include rlm@0: rlm@0: #include "AL/al.h" rlm@0: #include "AL/alc.h" rlm@0: #include "alMain.h" rlm@0: #include "alAuxEffectSlot.h" rlm@0: #include "alEffect.h" rlm@0: #include "alError.h" rlm@0: #include "alu.h" rlm@0: rlm@0: typedef struct DelayLine rlm@0: { rlm@0: // The delay lines use sample lengths that are powers of 2 to allow the rlm@0: // use of bit-masking instead of a modulus for wrapping. rlm@0: ALuint Mask; rlm@0: ALfloat *Line; rlm@0: } DelayLine; rlm@0: rlm@0: typedef struct ALverbState { rlm@0: // Must be first in all effects! rlm@0: ALeffectState state; rlm@0: rlm@0: // All delay lines are allocated as a single buffer to reduce memory rlm@0: // fragmentation and management code. rlm@0: ALfloat *SampleBuffer; rlm@0: ALuint TotalSamples; rlm@0: // Master effect low-pass filter (2 chained 1-pole filters). rlm@0: FILTER LpFilter; rlm@0: ALfloat LpHistory[2]; rlm@0: struct { rlm@0: // Modulator delay line. rlm@0: DelayLine Delay; rlm@0: // The vibrato time is tracked with an index over a modulus-wrapped rlm@0: // range (in samples). rlm@0: ALuint Index; rlm@0: ALuint Range; rlm@0: // The depth of frequency change (also in samples) and its filter. rlm@0: ALfloat Depth; rlm@0: ALfloat Coeff; rlm@0: ALfloat Filter; rlm@0: } Mod; rlm@0: // Initial effect delay. rlm@0: DelayLine Delay; rlm@0: // The tap points for the initial delay. First tap goes to early rlm@0: // reflections, the last to late reverb. rlm@0: ALuint DelayTap[2]; rlm@0: struct { rlm@0: // Output gain for early reflections. rlm@0: ALfloat Gain; rlm@0: // Early reflections are done with 4 delay lines. rlm@0: ALfloat Coeff[4]; rlm@0: DelayLine Delay[4]; rlm@0: ALuint Offset[4]; rlm@0: // The gain for each output channel based on 3D panning (only for the rlm@0: // EAX path). rlm@0: ALfloat PanGain[MAXCHANNELS]; rlm@0: } Early; rlm@0: // Decorrelator delay line. rlm@0: DelayLine Decorrelator; rlm@0: // There are actually 4 decorrelator taps, but the first occurs at the rlm@0: // initial sample. rlm@0: ALuint DecoTap[3]; rlm@0: struct { rlm@0: // Output gain for late reverb. rlm@0: ALfloat Gain; rlm@0: // Attenuation to compensate for the modal density and decay rate of rlm@0: // the late lines. rlm@0: ALfloat DensityGain; rlm@0: // The feed-back and feed-forward all-pass coefficient. rlm@0: ALfloat ApFeedCoeff; rlm@0: // Mixing matrix coefficient. rlm@0: ALfloat MixCoeff; rlm@0: // Late reverb has 4 parallel all-pass filters. rlm@0: ALfloat ApCoeff[4]; rlm@0: DelayLine ApDelay[4]; rlm@0: ALuint ApOffset[4]; rlm@0: // In addition to 4 cyclical delay lines. rlm@0: ALfloat Coeff[4]; rlm@0: DelayLine Delay[4]; rlm@0: ALuint Offset[4]; rlm@0: // The cyclical delay lines are 1-pole low-pass filtered. rlm@0: ALfloat LpCoeff[4]; rlm@0: ALfloat LpSample[4]; rlm@0: // The gain for each output channel based on 3D panning (only for the rlm@0: // EAX path). rlm@0: ALfloat PanGain[MAXCHANNELS]; rlm@0: } Late; rlm@0: struct { rlm@0: // Attenuation to compensate for the modal density and decay rate of rlm@0: // the echo line. rlm@0: ALfloat DensityGain; rlm@0: // Echo delay and all-pass lines. rlm@0: DelayLine Delay; rlm@0: DelayLine ApDelay; rlm@0: ALfloat Coeff; rlm@0: ALfloat ApFeedCoeff; rlm@0: ALfloat ApCoeff; rlm@0: ALuint Offset; rlm@0: ALuint ApOffset; rlm@0: // The echo line is 1-pole low-pass filtered. rlm@0: ALfloat LpCoeff; rlm@0: ALfloat LpSample; rlm@0: // Echo mixing coefficients. rlm@0: ALfloat MixCoeff[2]; rlm@0: } Echo; rlm@0: // The current read offset for all delay lines. rlm@0: ALuint Offset; rlm@0: rlm@0: // The gain for each output channel (non-EAX path only; aliased from rlm@0: // Late.PanGain) rlm@0: ALfloat *Gain; rlm@0: } ALverbState; rlm@0: rlm@0: /* This is a user config option for modifying the overall output of the reverb rlm@0: * effect. rlm@0: */ rlm@0: ALfloat ReverbBoost = 1.0f; rlm@0: rlm@0: /* Specifies whether to use a standard reverb effect in place of EAX reverb */ rlm@0: ALboolean EmulateEAXReverb = AL_FALSE; rlm@0: rlm@0: /* This coefficient is used to define the maximum frequency range controlled rlm@0: * by the modulation depth. The current value of 0.1 will allow it to swing rlm@0: * from 0.9x to 1.1x. This value must be below 1. At 1 it will cause the rlm@0: * sampler to stall on the downswing, and above 1 it will cause it to sample rlm@0: * backwards. rlm@0: */ rlm@0: static const ALfloat MODULATION_DEPTH_COEFF = 0.1f; rlm@0: rlm@0: /* A filter is used to avoid the terrible distortion caused by changing rlm@0: * modulation time and/or depth. To be consistent across different sample rlm@0: * rates, the coefficient must be raised to a constant divided by the sample rlm@0: * rate: coeff^(constant / rate). rlm@0: */ rlm@0: static const ALfloat MODULATION_FILTER_COEFF = 0.048f; rlm@0: static const ALfloat MODULATION_FILTER_CONST = 100000.0f; rlm@0: rlm@0: // When diffusion is above 0, an all-pass filter is used to take the edge off rlm@0: // the echo effect. It uses the following line length (in seconds). rlm@0: static const ALfloat ECHO_ALLPASS_LENGTH = 0.0133f; rlm@0: rlm@0: // Input into the late reverb is decorrelated between four channels. Their rlm@0: // timings are dependent on a fraction and multiplier. See the rlm@0: // UpdateDecorrelator() routine for the calculations involved. rlm@0: static const ALfloat DECO_FRACTION = 0.15f; rlm@0: static const ALfloat DECO_MULTIPLIER = 2.0f; rlm@0: rlm@0: // All delay line lengths are specified in seconds. rlm@0: rlm@0: // The lengths of the early delay lines. rlm@0: static const ALfloat EARLY_LINE_LENGTH[4] = rlm@0: { rlm@0: 0.0015f, 0.0045f, 0.0135f, 0.0405f rlm@0: }; rlm@0: rlm@0: // The lengths of the late all-pass delay lines. rlm@0: static const ALfloat ALLPASS_LINE_LENGTH[4] = rlm@0: { rlm@0: 0.0151f, 0.0167f, 0.0183f, 0.0200f, rlm@0: }; rlm@0: rlm@0: // The lengths of the late cyclical delay lines. rlm@0: static const ALfloat LATE_LINE_LENGTH[4] = rlm@0: { rlm@0: 0.0211f, 0.0311f, 0.0461f, 0.0680f rlm@0: }; rlm@0: rlm@0: // The late cyclical delay lines have a variable length dependent on the rlm@0: // effect's density parameter (inverted for some reason) and this multiplier. rlm@0: static const ALfloat LATE_LINE_MULTIPLIER = 4.0f; rlm@0: rlm@0: // Calculate the length of a delay line and store its mask and offset. rlm@0: static ALuint CalcLineLength(ALfloat length, ALintptrEXT offset, ALuint frequency, DelayLine *Delay) rlm@0: { rlm@0: ALuint samples; rlm@0: rlm@0: // All line lengths are powers of 2, calculated from their lengths, with rlm@0: // an additional sample in case of rounding errors. rlm@0: samples = NextPowerOf2((ALuint)(length * frequency) + 1); rlm@0: // All lines share a single sample buffer. rlm@0: Delay->Mask = samples - 1; rlm@0: Delay->Line = (ALfloat*)offset; rlm@0: // Return the sample count for accumulation. rlm@0: return samples; rlm@0: } rlm@0: rlm@0: // Given the allocated sample buffer, this function updates each delay line rlm@0: // offset. rlm@0: static __inline ALvoid RealizeLineOffset(ALfloat * sampleBuffer, DelayLine *Delay) rlm@0: { rlm@0: Delay->Line = &sampleBuffer[(ALintptrEXT)Delay->Line]; rlm@0: } rlm@0: rlm@0: /* Calculates the delay line metrics and allocates the shared sample buffer rlm@0: * for all lines given a flag indicating whether or not to allocate the EAX- rlm@0: * related delays (eaxFlag) and the sample rate (frequency). If an rlm@0: * allocation failure occurs, it returns AL_FALSE. rlm@0: */ rlm@0: static ALboolean AllocLines(ALboolean eaxFlag, ALuint frequency, ALverbState *State) rlm@0: { rlm@0: ALuint totalSamples, index; rlm@0: ALfloat length; rlm@0: ALfloat *newBuffer = NULL; rlm@0: rlm@0: // All delay line lengths are calculated to accomodate the full range of rlm@0: // lengths given their respective paramters. rlm@0: totalSamples = 0; rlm@0: if(eaxFlag) rlm@0: { rlm@0: /* The modulator's line length is calculated from the maximum rlm@0: * modulation time and depth coefficient, and halfed for the low-to- rlm@0: * high frequency swing. An additional sample is added to keep it rlm@0: * stable when there is no modulation. rlm@0: */ rlm@0: length = (AL_EAXREVERB_MAX_MODULATION_TIME * MODULATION_DEPTH_COEFF / rlm@0: 2.0f) + (1.0f / frequency); rlm@0: totalSamples += CalcLineLength(length, totalSamples, frequency, rlm@0: &State->Mod.Delay); rlm@0: } rlm@0: rlm@0: // The initial delay is the sum of the reflections and late reverb rlm@0: // delays. rlm@0: if(eaxFlag) rlm@0: length = AL_EAXREVERB_MAX_REFLECTIONS_DELAY + rlm@0: AL_EAXREVERB_MAX_LATE_REVERB_DELAY; rlm@0: else rlm@0: length = AL_REVERB_MAX_REFLECTIONS_DELAY + rlm@0: AL_REVERB_MAX_LATE_REVERB_DELAY; rlm@0: totalSamples += CalcLineLength(length, totalSamples, frequency, rlm@0: &State->Delay); rlm@0: rlm@0: // The early reflection lines. rlm@0: for(index = 0;index < 4;index++) rlm@0: totalSamples += CalcLineLength(EARLY_LINE_LENGTH[index], totalSamples, rlm@0: frequency, &State->Early.Delay[index]); rlm@0: rlm@0: // The decorrelator line is calculated from the lowest reverb density (a rlm@0: // parameter value of 1). rlm@0: length = (DECO_FRACTION * DECO_MULTIPLIER * DECO_MULTIPLIER) * rlm@0: LATE_LINE_LENGTH[0] * (1.0f + LATE_LINE_MULTIPLIER); rlm@0: totalSamples += CalcLineLength(length, totalSamples, frequency, rlm@0: &State->Decorrelator); rlm@0: rlm@0: // The late all-pass lines. rlm@0: for(index = 0;index < 4;index++) rlm@0: totalSamples += CalcLineLength(ALLPASS_LINE_LENGTH[index], totalSamples, rlm@0: frequency, &State->Late.ApDelay[index]); rlm@0: rlm@0: // The late delay lines are calculated from the lowest reverb density. rlm@0: for(index = 0;index < 4;index++) rlm@0: { rlm@0: length = LATE_LINE_LENGTH[index] * (1.0f + LATE_LINE_MULTIPLIER); rlm@0: totalSamples += CalcLineLength(length, totalSamples, frequency, rlm@0: &State->Late.Delay[index]); rlm@0: } rlm@0: rlm@0: if(eaxFlag) rlm@0: { rlm@0: // The echo all-pass and delay lines. rlm@0: totalSamples += CalcLineLength(ECHO_ALLPASS_LENGTH, totalSamples, rlm@0: frequency, &State->Echo.ApDelay); rlm@0: totalSamples += CalcLineLength(AL_EAXREVERB_MAX_ECHO_TIME, totalSamples, rlm@0: frequency, &State->Echo.Delay); rlm@0: } rlm@0: rlm@0: if(totalSamples != State->TotalSamples) rlm@0: { rlm@0: newBuffer = realloc(State->SampleBuffer, sizeof(ALfloat) * totalSamples); rlm@0: if(newBuffer == NULL) rlm@0: return AL_FALSE; rlm@0: State->SampleBuffer = newBuffer; rlm@0: State->TotalSamples = totalSamples; rlm@0: } rlm@0: rlm@0: // Update all delays to reflect the new sample buffer. rlm@0: RealizeLineOffset(State->SampleBuffer, &State->Delay); rlm@0: RealizeLineOffset(State->SampleBuffer, &State->Decorrelator); rlm@0: for(index = 0;index < 4;index++) rlm@0: { rlm@0: RealizeLineOffset(State->SampleBuffer, &State->Early.Delay[index]); rlm@0: RealizeLineOffset(State->SampleBuffer, &State->Late.ApDelay[index]); rlm@0: RealizeLineOffset(State->SampleBuffer, &State->Late.Delay[index]); rlm@0: } rlm@0: if(eaxFlag) rlm@0: { rlm@0: RealizeLineOffset(State->SampleBuffer, &State->Mod.Delay); rlm@0: RealizeLineOffset(State->SampleBuffer, &State->Echo.ApDelay); rlm@0: RealizeLineOffset(State->SampleBuffer, &State->Echo.Delay); rlm@0: } rlm@0: rlm@0: // Clear the sample buffer. rlm@0: for(index = 0;index < State->TotalSamples;index++) rlm@0: State->SampleBuffer[index] = 0.0f; rlm@0: rlm@0: return AL_TRUE; rlm@0: } rlm@0: rlm@0: // Calculate a decay coefficient given the length of each cycle and the time rlm@0: // until the decay reaches -60 dB. rlm@0: static __inline ALfloat CalcDecayCoeff(ALfloat length, ALfloat decayTime) rlm@0: { rlm@0: return aluPow(0.001f/*-60 dB*/, length/decayTime); rlm@0: } rlm@0: rlm@0: // Calculate a decay length from a coefficient and the time until the decay rlm@0: // reaches -60 dB. rlm@0: static __inline ALfloat CalcDecayLength(ALfloat coeff, ALfloat decayTime) rlm@0: { rlm@0: return log10(coeff) * decayTime / -3.0f/*log10(0.001)*/; rlm@0: } rlm@0: rlm@0: // Calculate the high frequency parameter for the I3DL2 coefficient rlm@0: // calculation. rlm@0: static __inline ALfloat CalcI3DL2HFreq(ALfloat hfRef, ALuint frequency) rlm@0: { rlm@0: return cos(2.0f * M_PI * hfRef / frequency); rlm@0: } rlm@0: rlm@0: // Calculate an attenuation to be applied to the input of any echo models to rlm@0: // compensate for modal density and decay time. rlm@0: static __inline ALfloat CalcDensityGain(ALfloat a) rlm@0: { rlm@0: /* The energy of a signal can be obtained by finding the area under the rlm@0: * squared signal. This takes the form of Sum(x_n^2), where x is the rlm@0: * amplitude for the sample n. rlm@0: * rlm@0: * Decaying feedback matches exponential decay of the form Sum(a^n), rlm@0: * where a is the attenuation coefficient, and n is the sample. The area rlm@0: * under this decay curve can be calculated as: 1 / (1 - a). rlm@0: * rlm@0: * Modifying the above equation to find the squared area under the curve rlm@0: * (for energy) yields: 1 / (1 - a^2). Input attenuation can then be rlm@0: * calculated by inverting the square root of this approximation, rlm@0: * yielding: 1 / sqrt(1 / (1 - a^2)), simplified to: sqrt(1 - a^2). rlm@0: */ rlm@0: return aluSqrt(1.0f - (a * a)); rlm@0: } rlm@0: rlm@0: // Calculate the mixing matrix coefficients given a diffusion factor. rlm@0: static __inline ALvoid CalcMatrixCoeffs(ALfloat diffusion, ALfloat *x, ALfloat *y) rlm@0: { rlm@0: ALfloat n, t; rlm@0: rlm@0: // The matrix is of order 4, so n is sqrt (4 - 1). rlm@0: n = aluSqrt(3.0f); rlm@0: t = diffusion * atan(n); rlm@0: rlm@0: // Calculate the first mixing matrix coefficient. rlm@0: *x = cos(t); rlm@0: // Calculate the second mixing matrix coefficient. rlm@0: *y = sin(t) / n; rlm@0: } rlm@0: rlm@0: // Calculate the limited HF ratio for use with the late reverb low-pass rlm@0: // filters. rlm@0: static ALfloat CalcLimitedHfRatio(ALfloat hfRatio, ALfloat airAbsorptionGainHF, ALfloat decayTime) rlm@0: { rlm@0: ALfloat limitRatio; rlm@0: rlm@0: /* Find the attenuation due to air absorption in dB (converting delay rlm@0: * time to meters using the speed of sound). Then reversing the decay rlm@0: * equation, solve for HF ratio. The delay length is cancelled out of rlm@0: * the equation, so it can be calculated once for all lines. rlm@0: */ rlm@0: limitRatio = 1.0f / (CalcDecayLength(airAbsorptionGainHF, decayTime) * rlm@0: SPEEDOFSOUNDMETRESPERSEC); rlm@0: /* Using the limit calculated above, apply the upper bound to the HF rlm@0: * ratio. Also need to limit the result to a minimum of 0.1, just like the rlm@0: * HF ratio parameter. */ rlm@0: return clampf(limitRatio, 0.1f, hfRatio); rlm@0: } rlm@0: rlm@0: // Calculate the coefficient for a HF (and eventually LF) decay damping rlm@0: // filter. rlm@0: static __inline ALfloat CalcDampingCoeff(ALfloat hfRatio, ALfloat length, ALfloat decayTime, ALfloat decayCoeff, ALfloat cw) rlm@0: { rlm@0: ALfloat coeff, g; rlm@0: rlm@0: // Eventually this should boost the high frequencies when the ratio rlm@0: // exceeds 1. rlm@0: coeff = 0.0f; rlm@0: if (hfRatio < 1.0f) rlm@0: { rlm@0: // Calculate the low-pass coefficient by dividing the HF decay rlm@0: // coefficient by the full decay coefficient. rlm@0: g = CalcDecayCoeff(length, decayTime * hfRatio) / decayCoeff; rlm@0: rlm@0: // Damping is done with a 1-pole filter, so g needs to be squared. rlm@0: g *= g; rlm@0: coeff = lpCoeffCalc(g, cw); rlm@0: rlm@0: // Very low decay times will produce minimal output, so apply an rlm@0: // upper bound to the coefficient. rlm@0: coeff = minf(coeff, 0.98f); rlm@0: } rlm@0: return coeff; rlm@0: } rlm@0: rlm@0: // Update the EAX modulation index, range, and depth. Keep in mind that this rlm@0: // kind of vibrato is additive and not multiplicative as one may expect. The rlm@0: // downswing will sound stronger than the upswing. rlm@0: static ALvoid UpdateModulator(ALfloat modTime, ALfloat modDepth, ALuint frequency, ALverbState *State) rlm@0: { rlm@0: ALfloat length; rlm@0: rlm@0: /* Modulation is calculated in two parts. rlm@0: * rlm@0: * The modulation time effects the sinus applied to the change in rlm@0: * frequency. An index out of the current time range (both in samples) rlm@0: * is incremented each sample. The range is bound to a reasonable rlm@0: * minimum (1 sample) and when the timing changes, the index is rescaled rlm@0: * to the new range (to keep the sinus consistent). rlm@0: */ rlm@0: length = modTime * frequency; rlm@0: if (length >= 1.0f) { rlm@0: State->Mod.Index = (ALuint)(State->Mod.Index * length / rlm@0: State->Mod.Range); rlm@0: State->Mod.Range = (ALuint)length; rlm@0: } else { rlm@0: State->Mod.Index = 0; rlm@0: State->Mod.Range = 1; rlm@0: } rlm@0: rlm@0: /* The modulation depth effects the amount of frequency change over the rlm@0: * range of the sinus. It needs to be scaled by the modulation time so rlm@0: * that a given depth produces a consistent change in frequency over all rlm@0: * ranges of time. Since the depth is applied to a sinus value, it needs rlm@0: * to be halfed once for the sinus range and again for the sinus swing rlm@0: * in time (half of it is spent decreasing the frequency, half is spent rlm@0: * increasing it). rlm@0: */ rlm@0: State->Mod.Depth = modDepth * MODULATION_DEPTH_COEFF * modTime / 2.0f / rlm@0: 2.0f * frequency; rlm@0: } rlm@0: rlm@0: // Update the offsets for the initial effect delay line. rlm@0: static ALvoid UpdateDelayLine(ALfloat earlyDelay, ALfloat lateDelay, ALuint frequency, ALverbState *State) rlm@0: { rlm@0: // Calculate the initial delay taps. rlm@0: State->DelayTap[0] = (ALuint)(earlyDelay * frequency); rlm@0: State->DelayTap[1] = (ALuint)((earlyDelay + lateDelay) * frequency); rlm@0: } rlm@0: rlm@0: // Update the early reflections gain and line coefficients. rlm@0: static ALvoid UpdateEarlyLines(ALfloat reverbGain, ALfloat earlyGain, ALfloat lateDelay, ALverbState *State) rlm@0: { rlm@0: ALuint index; rlm@0: rlm@0: // Calculate the early reflections gain (from the master effect gain, and rlm@0: // reflections gain parameters) with a constant attenuation of 0.5. rlm@0: State->Early.Gain = 0.5f * reverbGain * earlyGain; rlm@0: rlm@0: // Calculate the gain (coefficient) for each early delay line using the rlm@0: // late delay time. This expands the early reflections to the start of rlm@0: // the late reverb. rlm@0: for(index = 0;index < 4;index++) rlm@0: State->Early.Coeff[index] = CalcDecayCoeff(EARLY_LINE_LENGTH[index], rlm@0: lateDelay); rlm@0: } rlm@0: rlm@0: // Update the offsets for the decorrelator line. rlm@0: static ALvoid UpdateDecorrelator(ALfloat density, ALuint frequency, ALverbState *State) rlm@0: { rlm@0: ALuint index; rlm@0: ALfloat length; rlm@0: rlm@0: /* The late reverb inputs are decorrelated to smooth the reverb tail and rlm@0: * reduce harsh echos. The first tap occurs immediately, while the rlm@0: * remaining taps are delayed by multiples of a fraction of the smallest rlm@0: * cyclical delay time. rlm@0: * rlm@0: * offset[index] = (FRACTION (MULTIPLIER^index)) smallest_delay rlm@0: */ rlm@0: for(index = 0;index < 3;index++) rlm@0: { rlm@0: length = (DECO_FRACTION * aluPow(DECO_MULTIPLIER, (ALfloat)index)) * rlm@0: LATE_LINE_LENGTH[0] * (1.0f + (density * LATE_LINE_MULTIPLIER)); rlm@0: State->DecoTap[index] = (ALuint)(length * frequency); rlm@0: } rlm@0: } rlm@0: rlm@0: // Update the late reverb gains, line lengths, and line coefficients. rlm@0: static ALvoid UpdateLateLines(ALfloat reverbGain, ALfloat lateGain, ALfloat xMix, ALfloat density, ALfloat decayTime, ALfloat diffusion, ALfloat hfRatio, ALfloat cw, ALuint frequency, ALverbState *State) rlm@0: { rlm@0: ALfloat length; rlm@0: ALuint index; rlm@0: rlm@0: /* Calculate the late reverb gain (from the master effect gain, and late rlm@0: * reverb gain parameters). Since the output is tapped prior to the rlm@0: * application of the next delay line coefficients, this gain needs to be rlm@0: * attenuated by the 'x' mixing matrix coefficient as well. rlm@0: */ rlm@0: State->Late.Gain = reverbGain * lateGain * xMix; rlm@0: rlm@0: /* To compensate for changes in modal density and decay time of the late rlm@0: * reverb signal, the input is attenuated based on the maximal energy of rlm@0: * the outgoing signal. This approximation is used to keep the apparent rlm@0: * energy of the signal equal for all ranges of density and decay time. rlm@0: * rlm@0: * The average length of the cyclcical delay lines is used to calculate rlm@0: * the attenuation coefficient. rlm@0: */ rlm@0: length = (LATE_LINE_LENGTH[0] + LATE_LINE_LENGTH[1] + rlm@0: LATE_LINE_LENGTH[2] + LATE_LINE_LENGTH[3]) / 4.0f; rlm@0: length *= 1.0f + (density * LATE_LINE_MULTIPLIER); rlm@0: State->Late.DensityGain = CalcDensityGain(CalcDecayCoeff(length, rlm@0: decayTime)); rlm@0: rlm@0: // Calculate the all-pass feed-back and feed-forward coefficient. rlm@0: State->Late.ApFeedCoeff = 0.5f * aluPow(diffusion, 2.0f); rlm@0: rlm@0: for(index = 0;index < 4;index++) rlm@0: { rlm@0: // Calculate the gain (coefficient) for each all-pass line. rlm@0: State->Late.ApCoeff[index] = CalcDecayCoeff(ALLPASS_LINE_LENGTH[index], rlm@0: decayTime); rlm@0: rlm@0: // Calculate the length (in seconds) of each cyclical delay line. rlm@0: length = LATE_LINE_LENGTH[index] * (1.0f + (density * rlm@0: LATE_LINE_MULTIPLIER)); rlm@0: rlm@0: // Calculate the delay offset for each cyclical delay line. rlm@0: State->Late.Offset[index] = (ALuint)(length * frequency); rlm@0: rlm@0: // Calculate the gain (coefficient) for each cyclical line. rlm@0: State->Late.Coeff[index] = CalcDecayCoeff(length, decayTime); rlm@0: rlm@0: // Calculate the damping coefficient for each low-pass filter. rlm@0: State->Late.LpCoeff[index] = rlm@0: CalcDampingCoeff(hfRatio, length, decayTime, rlm@0: State->Late.Coeff[index], cw); rlm@0: rlm@0: // Attenuate the cyclical line coefficients by the mixing coefficient rlm@0: // (x). rlm@0: State->Late.Coeff[index] *= xMix; rlm@0: } rlm@0: } rlm@0: rlm@0: // Update the echo gain, line offset, line coefficients, and mixing rlm@0: // coefficients. rlm@0: static ALvoid UpdateEchoLine(ALfloat reverbGain, ALfloat lateGain, ALfloat echoTime, ALfloat decayTime, ALfloat diffusion, ALfloat echoDepth, ALfloat hfRatio, ALfloat cw, ALuint frequency, ALverbState *State) rlm@0: { rlm@0: // Update the offset and coefficient for the echo delay line. rlm@0: State->Echo.Offset = (ALuint)(echoTime * frequency); rlm@0: rlm@0: // Calculate the decay coefficient for the echo line. rlm@0: State->Echo.Coeff = CalcDecayCoeff(echoTime, decayTime); rlm@0: rlm@0: // Calculate the energy-based attenuation coefficient for the echo delay rlm@0: // line. rlm@0: State->Echo.DensityGain = CalcDensityGain(State->Echo.Coeff); rlm@0: rlm@0: // Calculate the echo all-pass feed coefficient. rlm@0: State->Echo.ApFeedCoeff = 0.5f * aluPow(diffusion, 2.0f); rlm@0: rlm@0: // Calculate the echo all-pass attenuation coefficient. rlm@0: State->Echo.ApCoeff = CalcDecayCoeff(ECHO_ALLPASS_LENGTH, decayTime); rlm@0: rlm@0: // Calculate the damping coefficient for each low-pass filter. rlm@0: State->Echo.LpCoeff = CalcDampingCoeff(hfRatio, echoTime, decayTime, rlm@0: State->Echo.Coeff, cw); rlm@0: rlm@0: /* Calculate the echo mixing coefficients. The first is applied to the rlm@0: * echo itself. The second is used to attenuate the late reverb when rlm@0: * echo depth is high and diffusion is low, so the echo is slightly rlm@0: * stronger than the decorrelated echos in the reverb tail. rlm@0: */ rlm@0: State->Echo.MixCoeff[0] = reverbGain * lateGain * echoDepth; rlm@0: State->Echo.MixCoeff[1] = 1.0f - (echoDepth * 0.5f * (1.0f - diffusion)); rlm@0: } rlm@0: rlm@0: // Update the early and late 3D panning gains. rlm@0: static ALvoid Update3DPanning(const ALCdevice *Device, const ALfloat *ReflectionsPan, const ALfloat *LateReverbPan, ALfloat Gain, ALverbState *State) rlm@0: { rlm@0: ALfloat earlyPan[3] = { ReflectionsPan[0], ReflectionsPan[1], rlm@0: ReflectionsPan[2] }; rlm@0: ALfloat latePan[3] = { LateReverbPan[0], LateReverbPan[1], rlm@0: LateReverbPan[2] }; rlm@0: const ALfloat *speakerGain; rlm@0: ALfloat ambientGain; rlm@0: ALfloat dirGain; rlm@0: ALfloat length; rlm@0: ALuint index; rlm@0: ALint pos; rlm@0: rlm@0: Gain *= ReverbBoost; rlm@0: rlm@0: // Attenuate non-directional reverb according to the number of channels rlm@0: ambientGain = aluSqrt(2.0f/Device->NumChan); rlm@0: rlm@0: // Calculate the 3D-panning gains for the early reflections and late rlm@0: // reverb. rlm@0: length = earlyPan[0]*earlyPan[0] + earlyPan[1]*earlyPan[1] + earlyPan[2]*earlyPan[2]; rlm@0: if(length > 1.0f) rlm@0: { rlm@0: length = 1.0f / aluSqrt(length); rlm@0: earlyPan[0] *= length; rlm@0: earlyPan[1] *= length; rlm@0: earlyPan[2] *= length; rlm@0: } rlm@0: length = latePan[0]*latePan[0] + latePan[1]*latePan[1] + latePan[2]*latePan[2]; rlm@0: if(length > 1.0f) rlm@0: { rlm@0: length = 1.0f / aluSqrt(length); rlm@0: latePan[0] *= length; rlm@0: latePan[1] *= length; rlm@0: latePan[2] *= length; rlm@0: } rlm@0: rlm@0: /* This code applies directional reverb just like the mixer applies rlm@0: * directional sources. It diffuses the sound toward all speakers as the rlm@0: * magnitude of the panning vector drops, which is only a rough rlm@0: * approximation of the expansion of sound across the speakers from the rlm@0: * panning direction. rlm@0: */ rlm@0: pos = aluCart2LUTpos(earlyPan[2], earlyPan[0]); rlm@0: speakerGain = Device->PanningLUT[pos]; rlm@0: dirGain = aluSqrt((earlyPan[0] * earlyPan[0]) + (earlyPan[2] * earlyPan[2])); rlm@0: rlm@0: for(index = 0;index < MAXCHANNELS;index++) rlm@0: State->Early.PanGain[index] = 0.0f; rlm@0: for(index = 0;index < Device->NumChan;index++) rlm@0: { rlm@0: enum Channel chan = Device->Speaker2Chan[index]; rlm@0: State->Early.PanGain[chan] = lerp(ambientGain, speakerGain[chan], dirGain) * Gain; rlm@0: } rlm@0: rlm@0: rlm@0: pos = aluCart2LUTpos(latePan[2], latePan[0]); rlm@0: speakerGain = Device->PanningLUT[pos]; rlm@0: dirGain = aluSqrt((latePan[0] * latePan[0]) + (latePan[2] * latePan[2])); rlm@0: rlm@0: for(index = 0;index < MAXCHANNELS;index++) rlm@0: State->Late.PanGain[index] = 0.0f; rlm@0: for(index = 0;index < Device->NumChan;index++) rlm@0: { rlm@0: enum Channel chan = Device->Speaker2Chan[index]; rlm@0: State->Late.PanGain[chan] = lerp(ambientGain, speakerGain[chan], dirGain) * Gain; rlm@0: } rlm@0: } rlm@0: rlm@0: // Basic delay line input/output routines. rlm@0: static __inline ALfloat DelayLineOut(DelayLine *Delay, ALuint offset) rlm@0: { rlm@0: return Delay->Line[offset&Delay->Mask]; rlm@0: } rlm@0: rlm@0: static __inline ALvoid DelayLineIn(DelayLine *Delay, ALuint offset, ALfloat in) rlm@0: { rlm@0: Delay->Line[offset&Delay->Mask] = in; rlm@0: } rlm@0: rlm@0: // Attenuated delay line output routine. rlm@0: static __inline ALfloat AttenuatedDelayLineOut(DelayLine *Delay, ALuint offset, ALfloat coeff) rlm@0: { rlm@0: return coeff * Delay->Line[offset&Delay->Mask]; rlm@0: } rlm@0: rlm@0: // Basic attenuated all-pass input/output routine. rlm@0: static __inline ALfloat AllpassInOut(DelayLine *Delay, ALuint outOffset, ALuint inOffset, ALfloat in, ALfloat feedCoeff, ALfloat coeff) rlm@0: { rlm@0: ALfloat out, feed; rlm@0: rlm@0: out = DelayLineOut(Delay, outOffset); rlm@0: feed = feedCoeff * in; rlm@0: DelayLineIn(Delay, inOffset, (feedCoeff * (out - feed)) + in); rlm@0: rlm@0: // The time-based attenuation is only applied to the delay output to rlm@0: // keep it from affecting the feed-back path (which is already controlled rlm@0: // by the all-pass feed coefficient). rlm@0: return (coeff * out) - feed; rlm@0: } rlm@0: rlm@0: // Given an input sample, this function produces modulation for the late rlm@0: // reverb. rlm@0: static __inline ALfloat EAXModulation(ALverbState *State, ALfloat in) rlm@0: { rlm@0: ALfloat sinus, frac; rlm@0: ALuint offset; rlm@0: ALfloat out0, out1; rlm@0: rlm@0: // Calculate the sinus rythm (dependent on modulation time and the rlm@0: // sampling rate). The center of the sinus is moved to reduce the delay rlm@0: // of the effect when the time or depth are low. rlm@0: sinus = 1.0f - cos(2.0f * M_PI * State->Mod.Index / State->Mod.Range); rlm@0: rlm@0: // The depth determines the range over which to read the input samples rlm@0: // from, so it must be filtered to reduce the distortion caused by even rlm@0: // small parameter changes. rlm@0: State->Mod.Filter = lerp(State->Mod.Filter, State->Mod.Depth, rlm@0: State->Mod.Coeff); rlm@0: rlm@0: // Calculate the read offset and fraction between it and the next sample. rlm@0: frac = (1.0f + (State->Mod.Filter * sinus)); rlm@0: offset = (ALuint)frac; rlm@0: frac -= offset; rlm@0: rlm@0: // Get the two samples crossed by the offset, and feed the delay line rlm@0: // with the next input sample. rlm@0: out0 = DelayLineOut(&State->Mod.Delay, State->Offset - offset); rlm@0: out1 = DelayLineOut(&State->Mod.Delay, State->Offset - offset - 1); rlm@0: DelayLineIn(&State->Mod.Delay, State->Offset, in); rlm@0: rlm@0: // Step the modulation index forward, keeping it bound to its range. rlm@0: State->Mod.Index = (State->Mod.Index + 1) % State->Mod.Range; rlm@0: rlm@0: // The output is obtained by linearly interpolating the two samples that rlm@0: // were acquired above. rlm@0: return lerp(out0, out1, frac); rlm@0: } rlm@0: rlm@0: // Delay line output routine for early reflections. rlm@0: static __inline ALfloat EarlyDelayLineOut(ALverbState *State, ALuint index) rlm@0: { rlm@0: return AttenuatedDelayLineOut(&State->Early.Delay[index], rlm@0: State->Offset - State->Early.Offset[index], rlm@0: State->Early.Coeff[index]); rlm@0: } rlm@0: rlm@0: // Given an input sample, this function produces four-channel output for the rlm@0: // early reflections. rlm@0: static __inline ALvoid EarlyReflection(ALverbState *State, ALfloat in, ALfloat *out) rlm@0: { rlm@0: ALfloat d[4], v, f[4]; rlm@0: rlm@0: // Obtain the decayed results of each early delay line. rlm@0: d[0] = EarlyDelayLineOut(State, 0); rlm@0: d[1] = EarlyDelayLineOut(State, 1); rlm@0: d[2] = EarlyDelayLineOut(State, 2); rlm@0: d[3] = EarlyDelayLineOut(State, 3); rlm@0: rlm@0: /* The following uses a lossless scattering junction from waveguide rlm@0: * theory. It actually amounts to a householder mixing matrix, which rlm@0: * will produce a maximally diffuse response, and means this can probably rlm@0: * be considered a simple feed-back delay network (FDN). rlm@0: * N rlm@0: * --- rlm@0: * \ rlm@0: * v = 2/N / d_i rlm@0: * --- rlm@0: * i=1 rlm@0: */ rlm@0: v = (d[0] + d[1] + d[2] + d[3]) * 0.5f; rlm@0: // The junction is loaded with the input here. rlm@0: v += in; rlm@0: rlm@0: // Calculate the feed values for the delay lines. rlm@0: f[0] = v - d[0]; rlm@0: f[1] = v - d[1]; rlm@0: f[2] = v - d[2]; rlm@0: f[3] = v - d[3]; rlm@0: rlm@0: // Re-feed the delay lines. rlm@0: DelayLineIn(&State->Early.Delay[0], State->Offset, f[0]); rlm@0: DelayLineIn(&State->Early.Delay[1], State->Offset, f[1]); rlm@0: DelayLineIn(&State->Early.Delay[2], State->Offset, f[2]); rlm@0: DelayLineIn(&State->Early.Delay[3], State->Offset, f[3]); rlm@0: rlm@0: // Output the results of the junction for all four channels. rlm@0: out[0] = State->Early.Gain * f[0]; rlm@0: out[1] = State->Early.Gain * f[1]; rlm@0: out[2] = State->Early.Gain * f[2]; rlm@0: out[3] = State->Early.Gain * f[3]; rlm@0: } rlm@0: rlm@0: // All-pass input/output routine for late reverb. rlm@0: static __inline ALfloat LateAllPassInOut(ALverbState *State, ALuint index, ALfloat in) rlm@0: { rlm@0: return AllpassInOut(&State->Late.ApDelay[index], rlm@0: State->Offset - State->Late.ApOffset[index], rlm@0: State->Offset, in, State->Late.ApFeedCoeff, rlm@0: State->Late.ApCoeff[index]); rlm@0: } rlm@0: rlm@0: // Delay line output routine for late reverb. rlm@0: static __inline ALfloat LateDelayLineOut(ALverbState *State, ALuint index) rlm@0: { rlm@0: return AttenuatedDelayLineOut(&State->Late.Delay[index], rlm@0: State->Offset - State->Late.Offset[index], rlm@0: State->Late.Coeff[index]); rlm@0: } rlm@0: rlm@0: // Low-pass filter input/output routine for late reverb. rlm@0: static __inline ALfloat LateLowPassInOut(ALverbState *State, ALuint index, ALfloat in) rlm@0: { rlm@0: in = lerp(in, State->Late.LpSample[index], State->Late.LpCoeff[index]); rlm@0: State->Late.LpSample[index] = in; rlm@0: return in; rlm@0: } rlm@0: rlm@0: // Given four decorrelated input samples, this function produces four-channel rlm@0: // output for the late reverb. rlm@0: static __inline ALvoid LateReverb(ALverbState *State, ALfloat *in, ALfloat *out) rlm@0: { rlm@0: ALfloat d[4], f[4]; rlm@0: rlm@0: // Obtain the decayed results of the cyclical delay lines, and add the rlm@0: // corresponding input channels. Then pass the results through the rlm@0: // low-pass filters. rlm@0: rlm@0: // This is where the feed-back cycles from line 0 to 1 to 3 to 2 and back rlm@0: // to 0. rlm@0: d[0] = LateLowPassInOut(State, 2, in[2] + LateDelayLineOut(State, 2)); rlm@0: d[1] = LateLowPassInOut(State, 0, in[0] + LateDelayLineOut(State, 0)); rlm@0: d[2] = LateLowPassInOut(State, 3, in[3] + LateDelayLineOut(State, 3)); rlm@0: d[3] = LateLowPassInOut(State, 1, in[1] + LateDelayLineOut(State, 1)); rlm@0: rlm@0: // To help increase diffusion, run each line through an all-pass filter. rlm@0: // When there is no diffusion, the shortest all-pass filter will feed the rlm@0: // shortest delay line. rlm@0: d[0] = LateAllPassInOut(State, 0, d[0]); rlm@0: d[1] = LateAllPassInOut(State, 1, d[1]); rlm@0: d[2] = LateAllPassInOut(State, 2, d[2]); rlm@0: d[3] = LateAllPassInOut(State, 3, d[3]); rlm@0: rlm@0: /* Late reverb is done with a modified feed-back delay network (FDN) rlm@0: * topology. Four input lines are each fed through their own all-pass rlm@0: * filter and then into the mixing matrix. The four outputs of the rlm@0: * mixing matrix are then cycled back to the inputs. Each output feeds rlm@0: * a different input to form a circlular feed cycle. rlm@0: * rlm@0: * The mixing matrix used is a 4D skew-symmetric rotation matrix derived rlm@0: * using a single unitary rotational parameter: rlm@0: * rlm@0: * [ d, a, b, c ] 1 = a^2 + b^2 + c^2 + d^2 rlm@0: * [ -a, d, c, -b ] rlm@0: * [ -b, -c, d, a ] rlm@0: * [ -c, b, -a, d ] rlm@0: * rlm@0: * The rotation is constructed from the effect's diffusion parameter, rlm@0: * yielding: 1 = x^2 + 3 y^2; where a, b, and c are the coefficient y rlm@0: * with differing signs, and d is the coefficient x. The matrix is thus: rlm@0: * rlm@0: * [ x, y, -y, y ] n = sqrt(matrix_order - 1) rlm@0: * [ -y, x, y, y ] t = diffusion_parameter * atan(n) rlm@0: * [ y, -y, x, y ] x = cos(t) rlm@0: * [ -y, -y, -y, x ] y = sin(t) / n rlm@0: * rlm@0: * To reduce the number of multiplies, the x coefficient is applied with rlm@0: * the cyclical delay line coefficients. Thus only the y coefficient is rlm@0: * applied when mixing, and is modified to be: y / x. rlm@0: */ rlm@0: f[0] = d[0] + (State->Late.MixCoeff * ( d[1] + -d[2] + d[3])); rlm@0: f[1] = d[1] + (State->Late.MixCoeff * (-d[0] + d[2] + d[3])); rlm@0: f[2] = d[2] + (State->Late.MixCoeff * ( d[0] + -d[1] + d[3])); rlm@0: f[3] = d[3] + (State->Late.MixCoeff * (-d[0] + -d[1] + -d[2] )); rlm@0: rlm@0: // Output the results of the matrix for all four channels, attenuated by rlm@0: // the late reverb gain (which is attenuated by the 'x' mix coefficient). rlm@0: out[0] = State->Late.Gain * f[0]; rlm@0: out[1] = State->Late.Gain * f[1]; rlm@0: out[2] = State->Late.Gain * f[2]; rlm@0: out[3] = State->Late.Gain * f[3]; rlm@0: rlm@0: // Re-feed the cyclical delay lines. rlm@0: DelayLineIn(&State->Late.Delay[0], State->Offset, f[0]); rlm@0: DelayLineIn(&State->Late.Delay[1], State->Offset, f[1]); rlm@0: DelayLineIn(&State->Late.Delay[2], State->Offset, f[2]); rlm@0: DelayLineIn(&State->Late.Delay[3], State->Offset, f[3]); rlm@0: } rlm@0: rlm@0: // Given an input sample, this function mixes echo into the four-channel late rlm@0: // reverb. rlm@0: static __inline ALvoid EAXEcho(ALverbState *State, ALfloat in, ALfloat *late) rlm@0: { rlm@0: ALfloat out, feed; rlm@0: rlm@0: // Get the latest attenuated echo sample for output. rlm@0: feed = AttenuatedDelayLineOut(&State->Echo.Delay, rlm@0: State->Offset - State->Echo.Offset, rlm@0: State->Echo.Coeff); rlm@0: rlm@0: // Mix the output into the late reverb channels. rlm@0: out = State->Echo.MixCoeff[0] * feed; rlm@0: late[0] = (State->Echo.MixCoeff[1] * late[0]) + out; rlm@0: late[1] = (State->Echo.MixCoeff[1] * late[1]) + out; rlm@0: late[2] = (State->Echo.MixCoeff[1] * late[2]) + out; rlm@0: late[3] = (State->Echo.MixCoeff[1] * late[3]) + out; rlm@0: rlm@0: // Mix the energy-attenuated input with the output and pass it through rlm@0: // the echo low-pass filter. rlm@0: feed += State->Echo.DensityGain * in; rlm@0: feed = lerp(feed, State->Echo.LpSample, State->Echo.LpCoeff); rlm@0: State->Echo.LpSample = feed; rlm@0: rlm@0: // Then the echo all-pass filter. rlm@0: feed = AllpassInOut(&State->Echo.ApDelay, rlm@0: State->Offset - State->Echo.ApOffset, rlm@0: State->Offset, feed, State->Echo.ApFeedCoeff, rlm@0: State->Echo.ApCoeff); rlm@0: rlm@0: // Feed the delay with the mixed and filtered sample. rlm@0: DelayLineIn(&State->Echo.Delay, State->Offset, feed); rlm@0: } rlm@0: rlm@0: // Perform the non-EAX reverb pass on a given input sample, resulting in rlm@0: // four-channel output. rlm@0: static __inline ALvoid VerbPass(ALverbState *State, ALfloat in, ALfloat *early, ALfloat *late) rlm@0: { rlm@0: ALfloat feed, taps[4]; rlm@0: rlm@0: // Low-pass filter the incoming sample. rlm@0: in = lpFilter2P(&State->LpFilter, 0, in); rlm@0: rlm@0: // Feed the initial delay line. rlm@0: DelayLineIn(&State->Delay, State->Offset, in); rlm@0: rlm@0: // Calculate the early reflection from the first delay tap. rlm@0: in = DelayLineOut(&State->Delay, State->Offset - State->DelayTap[0]); rlm@0: EarlyReflection(State, in, early); rlm@0: rlm@0: // Feed the decorrelator from the energy-attenuated output of the second rlm@0: // delay tap. rlm@0: in = DelayLineOut(&State->Delay, State->Offset - State->DelayTap[1]); rlm@0: feed = in * State->Late.DensityGain; rlm@0: DelayLineIn(&State->Decorrelator, State->Offset, feed); rlm@0: rlm@0: // Calculate the late reverb from the decorrelator taps. rlm@0: taps[0] = feed; rlm@0: taps[1] = DelayLineOut(&State->Decorrelator, State->Offset - State->DecoTap[0]); rlm@0: taps[2] = DelayLineOut(&State->Decorrelator, State->Offset - State->DecoTap[1]); rlm@0: taps[3] = DelayLineOut(&State->Decorrelator, State->Offset - State->DecoTap[2]); rlm@0: LateReverb(State, taps, late); rlm@0: rlm@0: // Step all delays forward one sample. rlm@0: State->Offset++; rlm@0: } rlm@0: rlm@0: // Perform the EAX reverb pass on a given input sample, resulting in four- rlm@0: // channel output. rlm@0: static __inline ALvoid EAXVerbPass(ALverbState *State, ALfloat in, ALfloat *early, ALfloat *late) rlm@0: { rlm@0: ALfloat feed, taps[4]; rlm@0: rlm@0: // Low-pass filter the incoming sample. rlm@0: in = lpFilter2P(&State->LpFilter, 0, in); rlm@0: rlm@0: // Perform any modulation on the input. rlm@0: in = EAXModulation(State, in); rlm@0: rlm@0: // Feed the initial delay line. rlm@0: DelayLineIn(&State->Delay, State->Offset, in); rlm@0: rlm@0: // Calculate the early reflection from the first delay tap. rlm@0: in = DelayLineOut(&State->Delay, State->Offset - State->DelayTap[0]); rlm@0: EarlyReflection(State, in, early); rlm@0: rlm@0: // Feed the decorrelator from the energy-attenuated output of the second rlm@0: // delay tap. rlm@0: in = DelayLineOut(&State->Delay, State->Offset - State->DelayTap[1]); rlm@0: feed = in * State->Late.DensityGain; rlm@0: DelayLineIn(&State->Decorrelator, State->Offset, feed); rlm@0: rlm@0: // Calculate the late reverb from the decorrelator taps. rlm@0: taps[0] = feed; rlm@0: taps[1] = DelayLineOut(&State->Decorrelator, State->Offset - State->DecoTap[0]); rlm@0: taps[2] = DelayLineOut(&State->Decorrelator, State->Offset - State->DecoTap[1]); rlm@0: taps[3] = DelayLineOut(&State->Decorrelator, State->Offset - State->DecoTap[2]); rlm@0: LateReverb(State, taps, late); rlm@0: rlm@0: // Calculate and mix in any echo. rlm@0: EAXEcho(State, in, late); rlm@0: rlm@0: // Step all delays forward one sample. rlm@0: State->Offset++; rlm@0: } rlm@0: rlm@0: // This destroys the reverb state. It should be called only when the effect rlm@0: // slot has a different (or no) effect loaded over the reverb effect. rlm@0: static ALvoid VerbDestroy(ALeffectState *effect) rlm@0: { rlm@0: ALverbState *State = (ALverbState*)effect; rlm@0: if(State) rlm@0: { rlm@0: free(State->SampleBuffer); rlm@0: State->SampleBuffer = NULL; rlm@0: free(State); rlm@0: } rlm@0: } rlm@0: rlm@0: // This updates the device-dependant reverb state. This is called on rlm@0: // initialization and any time the device parameters (eg. playback frequency, rlm@0: // or format) have been changed. rlm@0: static ALboolean VerbDeviceUpdate(ALeffectState *effect, ALCdevice *Device) rlm@0: { rlm@0: ALverbState *State = (ALverbState*)effect; rlm@0: ALuint frequency = Device->Frequency; rlm@0: ALuint index; rlm@0: rlm@0: // Allocate the delay lines. rlm@0: if(!AllocLines(AL_FALSE, frequency, State)) rlm@0: return AL_FALSE; rlm@0: rlm@0: // The early reflection and late all-pass filter line lengths are static, rlm@0: // so their offsets only need to be calculated once. rlm@0: for(index = 0;index < 4;index++) rlm@0: { rlm@0: State->Early.Offset[index] = (ALuint)(EARLY_LINE_LENGTH[index] * rlm@0: frequency); rlm@0: State->Late.ApOffset[index] = (ALuint)(ALLPASS_LINE_LENGTH[index] * rlm@0: frequency); rlm@0: } rlm@0: rlm@0: return AL_TRUE; rlm@0: } rlm@0: rlm@0: // This updates the device-dependant EAX reverb state. This is called on rlm@0: // initialization and any time the device parameters (eg. playback frequency, rlm@0: // format) have been changed. rlm@0: static ALboolean EAXVerbDeviceUpdate(ALeffectState *effect, ALCdevice *Device) rlm@0: { rlm@0: ALverbState *State = (ALverbState*)effect; rlm@0: ALuint frequency = Device->Frequency, index; rlm@0: rlm@0: // Allocate the delay lines. rlm@0: if(!AllocLines(AL_TRUE, frequency, State)) rlm@0: return AL_FALSE; rlm@0: rlm@0: // Calculate the modulation filter coefficient. Notice that the exponent rlm@0: // is calculated given the current sample rate. This ensures that the rlm@0: // resulting filter response over time is consistent across all sample rlm@0: // rates. rlm@0: State->Mod.Coeff = aluPow(MODULATION_FILTER_COEFF, rlm@0: MODULATION_FILTER_CONST / frequency); rlm@0: rlm@0: // The early reflection and late all-pass filter line lengths are static, rlm@0: // so their offsets only need to be calculated once. rlm@0: for(index = 0;index < 4;index++) rlm@0: { rlm@0: State->Early.Offset[index] = (ALuint)(EARLY_LINE_LENGTH[index] * rlm@0: frequency); rlm@0: State->Late.ApOffset[index] = (ALuint)(ALLPASS_LINE_LENGTH[index] * rlm@0: frequency); rlm@0: } rlm@0: rlm@0: // The echo all-pass filter line length is static, so its offset only rlm@0: // needs to be calculated once. rlm@0: State->Echo.ApOffset = (ALuint)(ECHO_ALLPASS_LENGTH * frequency); rlm@0: rlm@0: return AL_TRUE; rlm@0: } rlm@0: rlm@0: // This updates the reverb state. This is called any time the reverb effect rlm@0: // is loaded into a slot. rlm@0: static ALvoid VerbUpdate(ALeffectState *effect, ALCcontext *Context, const ALeffectslot *Slot) rlm@0: { rlm@0: ALverbState *State = (ALverbState*)effect; rlm@0: ALCdevice *Device = Context->Device; rlm@0: ALuint frequency = Device->Frequency; rlm@0: ALfloat cw, x, y, hfRatio, gain; rlm@0: ALuint index; rlm@0: rlm@0: // Calculate the master low-pass filter (from the master effect HF gain). rlm@0: cw = CalcI3DL2HFreq(Slot->effect.Params.Reverb.HFReference, frequency); rlm@0: // This is done with 2 chained 1-pole filters, so no need to square g. rlm@0: State->LpFilter.coeff = lpCoeffCalc(Slot->effect.Params.Reverb.GainHF, cw); rlm@0: rlm@0: // Update the initial effect delay. rlm@0: UpdateDelayLine(Slot->effect.Params.Reverb.ReflectionsDelay, rlm@0: Slot->effect.Params.Reverb.LateReverbDelay, rlm@0: frequency, State); rlm@0: rlm@0: // Update the early lines. rlm@0: UpdateEarlyLines(Slot->effect.Params.Reverb.Gain, rlm@0: Slot->effect.Params.Reverb.ReflectionsGain, rlm@0: Slot->effect.Params.Reverb.LateReverbDelay, State); rlm@0: rlm@0: // Update the decorrelator. rlm@0: UpdateDecorrelator(Slot->effect.Params.Reverb.Density, frequency, State); rlm@0: rlm@0: // Get the mixing matrix coefficients (x and y). rlm@0: CalcMatrixCoeffs(Slot->effect.Params.Reverb.Diffusion, &x, &y); rlm@0: // Then divide x into y to simplify the matrix calculation. rlm@0: State->Late.MixCoeff = y / x; rlm@0: rlm@0: // If the HF limit parameter is flagged, calculate an appropriate limit rlm@0: // based on the air absorption parameter. rlm@0: hfRatio = Slot->effect.Params.Reverb.DecayHFRatio; rlm@0: if(Slot->effect.Params.Reverb.DecayHFLimit && rlm@0: Slot->effect.Params.Reverb.AirAbsorptionGainHF < 1.0f) rlm@0: hfRatio = CalcLimitedHfRatio(hfRatio, rlm@0: Slot->effect.Params.Reverb.AirAbsorptionGainHF, rlm@0: Slot->effect.Params.Reverb.DecayTime); rlm@0: rlm@0: // Update the late lines. rlm@0: UpdateLateLines(Slot->effect.Params.Reverb.Gain, Slot->effect.Params.Reverb.LateReverbGain, rlm@0: x, Slot->effect.Params.Reverb.Density, Slot->effect.Params.Reverb.DecayTime, rlm@0: Slot->effect.Params.Reverb.Diffusion, hfRatio, cw, frequency, State); rlm@0: rlm@0: // Update channel gains rlm@0: gain = Slot->Gain; rlm@0: gain *= aluSqrt(2.0f/Device->NumChan); rlm@0: gain *= ReverbBoost; rlm@0: for(index = 0;index < MAXCHANNELS;index++) rlm@0: State->Gain[index] = 0.0f; rlm@0: for(index = 0;index < Device->NumChan;index++) rlm@0: { rlm@0: enum Channel chan = Device->Speaker2Chan[index]; rlm@0: State->Gain[chan] = gain; rlm@0: } rlm@0: } rlm@0: rlm@0: // This updates the EAX reverb state. This is called any time the EAX reverb rlm@0: // effect is loaded into a slot. rlm@0: static ALvoid EAXVerbUpdate(ALeffectState *effect, ALCcontext *Context, const ALeffectslot *Slot) rlm@0: { rlm@0: ALverbState *State = (ALverbState*)effect; rlm@0: ALuint frequency = Context->Device->Frequency; rlm@0: ALfloat cw, x, y, hfRatio; rlm@0: rlm@0: // Calculate the master low-pass filter (from the master effect HF gain). rlm@0: cw = CalcI3DL2HFreq(Slot->effect.Params.Reverb.HFReference, frequency); rlm@0: // This is done with 2 chained 1-pole filters, so no need to square g. rlm@0: State->LpFilter.coeff = lpCoeffCalc(Slot->effect.Params.Reverb.GainHF, cw); rlm@0: rlm@0: // Update the modulator line. rlm@0: UpdateModulator(Slot->effect.Params.Reverb.ModulationTime, rlm@0: Slot->effect.Params.Reverb.ModulationDepth, rlm@0: frequency, State); rlm@0: rlm@0: // Update the initial effect delay. rlm@0: UpdateDelayLine(Slot->effect.Params.Reverb.ReflectionsDelay, rlm@0: Slot->effect.Params.Reverb.LateReverbDelay, rlm@0: frequency, State); rlm@0: rlm@0: // Update the early lines. rlm@0: UpdateEarlyLines(Slot->effect.Params.Reverb.Gain, rlm@0: Slot->effect.Params.Reverb.ReflectionsGain, rlm@0: Slot->effect.Params.Reverb.LateReverbDelay, State); rlm@0: rlm@0: // Update the decorrelator. rlm@0: UpdateDecorrelator(Slot->effect.Params.Reverb.Density, frequency, State); rlm@0: rlm@0: // Get the mixing matrix coefficients (x and y). rlm@0: CalcMatrixCoeffs(Slot->effect.Params.Reverb.Diffusion, &x, &y); rlm@0: // Then divide x into y to simplify the matrix calculation. rlm@0: State->Late.MixCoeff = y / x; rlm@0: rlm@0: // If the HF limit parameter is flagged, calculate an appropriate limit rlm@0: // based on the air absorption parameter. rlm@0: hfRatio = Slot->effect.Params.Reverb.DecayHFRatio; rlm@0: if(Slot->effect.Params.Reverb.DecayHFLimit && rlm@0: Slot->effect.Params.Reverb.AirAbsorptionGainHF < 1.0f) rlm@0: hfRatio = CalcLimitedHfRatio(hfRatio, rlm@0: Slot->effect.Params.Reverb.AirAbsorptionGainHF, rlm@0: Slot->effect.Params.Reverb.DecayTime); rlm@0: rlm@0: // Update the late lines. rlm@0: UpdateLateLines(Slot->effect.Params.Reverb.Gain, Slot->effect.Params.Reverb.LateReverbGain, rlm@0: x, Slot->effect.Params.Reverb.Density, Slot->effect.Params.Reverb.DecayTime, rlm@0: Slot->effect.Params.Reverb.Diffusion, hfRatio, cw, frequency, State); rlm@0: rlm@0: // Update the echo line. rlm@0: UpdateEchoLine(Slot->effect.Params.Reverb.Gain, Slot->effect.Params.Reverb.LateReverbGain, rlm@0: Slot->effect.Params.Reverb.EchoTime, Slot->effect.Params.Reverb.DecayTime, rlm@0: Slot->effect.Params.Reverb.Diffusion, Slot->effect.Params.Reverb.EchoDepth, rlm@0: hfRatio, cw, frequency, State); rlm@0: rlm@0: // Update early and late 3D panning. rlm@0: Update3DPanning(Context->Device, Slot->effect.Params.Reverb.ReflectionsPan, rlm@0: Slot->effect.Params.Reverb.LateReverbPan, Slot->Gain, State); rlm@0: } rlm@0: rlm@0: // This processes the reverb state, given the input samples and an output rlm@0: // buffer. rlm@0: static ALvoid VerbProcess(ALeffectState *effect, const ALeffectslot *Slot, ALuint SamplesToDo, const ALfloat *SamplesIn, ALfloat (*SamplesOut)[MAXCHANNELS]) rlm@0: { rlm@0: ALverbState *State = (ALverbState*)effect; rlm@0: ALuint index; rlm@0: ALfloat early[4], late[4], out[4]; rlm@0: const ALfloat *panGain = State->Gain; rlm@0: (void)Slot; rlm@0: rlm@0: for(index = 0;index < SamplesToDo;index++) rlm@0: { rlm@0: // Process reverb for this sample. rlm@0: VerbPass(State, SamplesIn[index], early, late); rlm@0: rlm@0: // Mix early reflections and late reverb. rlm@0: out[0] = (early[0] + late[0]); rlm@0: out[1] = (early[1] + late[1]); rlm@0: out[2] = (early[2] + late[2]); rlm@0: out[3] = (early[3] + late[3]); rlm@0: rlm@0: // Output the results. rlm@0: SamplesOut[index][FRONT_LEFT] += panGain[FRONT_LEFT] * out[0]; rlm@0: SamplesOut[index][FRONT_RIGHT] += panGain[FRONT_RIGHT] * out[1]; rlm@0: SamplesOut[index][FRONT_CENTER] += panGain[FRONT_CENTER] * out[3]; rlm@0: SamplesOut[index][SIDE_LEFT] += panGain[SIDE_LEFT] * out[0]; rlm@0: SamplesOut[index][SIDE_RIGHT] += panGain[SIDE_RIGHT] * out[1]; rlm@0: SamplesOut[index][BACK_LEFT] += panGain[BACK_LEFT] * out[0]; rlm@0: SamplesOut[index][BACK_RIGHT] += panGain[BACK_RIGHT] * out[1]; rlm@0: SamplesOut[index][BACK_CENTER] += panGain[BACK_CENTER] * out[2]; rlm@0: } rlm@0: } rlm@0: rlm@0: // This processes the EAX reverb state, given the input samples and an output rlm@0: // buffer. rlm@0: static ALvoid EAXVerbProcess(ALeffectState *effect, const ALeffectslot *Slot, ALuint SamplesToDo, const ALfloat *SamplesIn, ALfloat (*SamplesOut)[MAXCHANNELS]) rlm@0: { rlm@0: ALverbState *State = (ALverbState*)effect; rlm@0: ALuint index; rlm@0: ALfloat early[4], late[4]; rlm@0: (void)Slot; rlm@0: rlm@0: for(index = 0;index < SamplesToDo;index++) rlm@0: { rlm@0: // Process reverb for this sample. rlm@0: EAXVerbPass(State, SamplesIn[index], early, late); rlm@0: rlm@0: // Unfortunately, while the number and configuration of gains for rlm@0: // panning adjust according to MAXCHANNELS, the output from the rlm@0: // reverb engine is not so scalable. rlm@0: SamplesOut[index][FRONT_LEFT] += rlm@0: (State->Early.PanGain[FRONT_LEFT]*early[0] + rlm@0: State->Late.PanGain[FRONT_LEFT]*late[0]); rlm@0: SamplesOut[index][FRONT_RIGHT] += rlm@0: (State->Early.PanGain[FRONT_RIGHT]*early[1] + rlm@0: State->Late.PanGain[FRONT_RIGHT]*late[1]); rlm@0: SamplesOut[index][FRONT_CENTER] += rlm@0: (State->Early.PanGain[FRONT_CENTER]*early[3] + rlm@0: State->Late.PanGain[FRONT_CENTER]*late[3]); rlm@0: SamplesOut[index][SIDE_LEFT] += rlm@0: (State->Early.PanGain[SIDE_LEFT]*early[0] + rlm@0: State->Late.PanGain[SIDE_LEFT]*late[0]); rlm@0: SamplesOut[index][SIDE_RIGHT] += rlm@0: (State->Early.PanGain[SIDE_RIGHT]*early[1] + rlm@0: State->Late.PanGain[SIDE_RIGHT]*late[1]); rlm@0: SamplesOut[index][BACK_LEFT] += rlm@0: (State->Early.PanGain[BACK_LEFT]*early[0] + rlm@0: State->Late.PanGain[BACK_LEFT]*late[0]); rlm@0: SamplesOut[index][BACK_RIGHT] += rlm@0: (State->Early.PanGain[BACK_RIGHT]*early[1] + rlm@0: State->Late.PanGain[BACK_RIGHT]*late[1]); rlm@0: SamplesOut[index][BACK_CENTER] += rlm@0: (State->Early.PanGain[BACK_CENTER]*early[2] + rlm@0: State->Late.PanGain[BACK_CENTER]*late[2]); rlm@0: } rlm@0: } rlm@0: rlm@0: // This creates the reverb state. It should be called only when the reverb rlm@0: // effect is loaded into a slot that doesn't already have a reverb effect. rlm@0: ALeffectState *VerbCreate(void) rlm@0: { rlm@0: ALverbState *State = NULL; rlm@0: ALuint index; rlm@0: rlm@0: State = malloc(sizeof(ALverbState)); rlm@0: if(!State) rlm@0: return NULL; rlm@0: rlm@0: State->state.Destroy = VerbDestroy; rlm@0: State->state.DeviceUpdate = VerbDeviceUpdate; rlm@0: State->state.Update = VerbUpdate; rlm@0: State->state.Process = VerbProcess; rlm@0: rlm@0: State->TotalSamples = 0; rlm@0: State->SampleBuffer = NULL; rlm@0: rlm@0: State->LpFilter.coeff = 0.0f; rlm@0: State->LpFilter.history[0] = 0.0f; rlm@0: State->LpFilter.history[1] = 0.0f; rlm@0: rlm@0: State->Mod.Delay.Mask = 0; rlm@0: State->Mod.Delay.Line = NULL; rlm@0: State->Mod.Index = 0; rlm@0: State->Mod.Range = 1; rlm@0: State->Mod.Depth = 0.0f; rlm@0: State->Mod.Coeff = 0.0f; rlm@0: State->Mod.Filter = 0.0f; rlm@0: rlm@0: State->Delay.Mask = 0; rlm@0: State->Delay.Line = NULL; rlm@0: State->DelayTap[0] = 0; rlm@0: State->DelayTap[1] = 0; rlm@0: rlm@0: State->Early.Gain = 0.0f; rlm@0: for(index = 0;index < 4;index++) rlm@0: { rlm@0: State->Early.Coeff[index] = 0.0f; rlm@0: State->Early.Delay[index].Mask = 0; rlm@0: State->Early.Delay[index].Line = NULL; rlm@0: State->Early.Offset[index] = 0; rlm@0: } rlm@0: rlm@0: State->Decorrelator.Mask = 0; rlm@0: State->Decorrelator.Line = NULL; rlm@0: State->DecoTap[0] = 0; rlm@0: State->DecoTap[1] = 0; rlm@0: State->DecoTap[2] = 0; rlm@0: rlm@0: State->Late.Gain = 0.0f; rlm@0: State->Late.DensityGain = 0.0f; rlm@0: State->Late.ApFeedCoeff = 0.0f; rlm@0: State->Late.MixCoeff = 0.0f; rlm@0: for(index = 0;index < 4;index++) rlm@0: { rlm@0: State->Late.ApCoeff[index] = 0.0f; rlm@0: State->Late.ApDelay[index].Mask = 0; rlm@0: State->Late.ApDelay[index].Line = NULL; rlm@0: State->Late.ApOffset[index] = 0; rlm@0: rlm@0: State->Late.Coeff[index] = 0.0f; rlm@0: State->Late.Delay[index].Mask = 0; rlm@0: State->Late.Delay[index].Line = NULL; rlm@0: State->Late.Offset[index] = 0; rlm@0: rlm@0: State->Late.LpCoeff[index] = 0.0f; rlm@0: State->Late.LpSample[index] = 0.0f; rlm@0: } rlm@0: rlm@0: for(index = 0;index < MAXCHANNELS;index++) rlm@0: { rlm@0: State->Early.PanGain[index] = 0.0f; rlm@0: State->Late.PanGain[index] = 0.0f; rlm@0: } rlm@0: rlm@0: State->Echo.DensityGain = 0.0f; rlm@0: State->Echo.Delay.Mask = 0; rlm@0: State->Echo.Delay.Line = NULL; rlm@0: State->Echo.ApDelay.Mask = 0; rlm@0: State->Echo.ApDelay.Line = NULL; rlm@0: State->Echo.Coeff = 0.0f; rlm@0: State->Echo.ApFeedCoeff = 0.0f; rlm@0: State->Echo.ApCoeff = 0.0f; rlm@0: State->Echo.Offset = 0; rlm@0: State->Echo.ApOffset = 0; rlm@0: State->Echo.LpCoeff = 0.0f; rlm@0: State->Echo.LpSample = 0.0f; rlm@0: State->Echo.MixCoeff[0] = 0.0f; rlm@0: State->Echo.MixCoeff[1] = 0.0f; rlm@0: rlm@0: State->Offset = 0; rlm@0: rlm@0: State->Gain = State->Late.PanGain; rlm@0: rlm@0: return &State->state; rlm@0: } rlm@0: rlm@0: ALeffectState *EAXVerbCreate(void) rlm@0: { rlm@0: ALeffectState *State = VerbCreate(); rlm@0: if(State && EmulateEAXReverb == AL_FALSE) rlm@0: { rlm@0: State->DeviceUpdate = EAXVerbDeviceUpdate; rlm@0: State->Update = EAXVerbUpdate; rlm@0: State->Process = EAXVerbProcess; rlm@0: } rlm@0: return State; rlm@0: }