aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Alc/effects/reverb.c867
1 files changed, 442 insertions, 425 deletions
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,421 +238,9 @@ 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)
-{
- return Delay->Line[offset&Delay->Mask];
-}
-
-static inline ALvoid DelayLineIn(DelayLine *Delay, ALuint offset, ALfloat in)
-{
- Delay->Line[offset&Delay->Mask] = in;
-}
-
-// Given an input sample, this function produces modulation for the late
-// reverb.
-static inline ALfloat EAXModulation(ALreverbState *State, ALuint offset, ALfloat in)
-{
- 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);
-
- // Step the modulation index forward, keeping it bound to its range.
- State->Mod.Index = (State->Mod.Index + 1) % State->Mod.Range;
-
- // 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);
-
- // 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);
-}
-
-// 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;
-
- for(i = 0;i < todo;i++)
- {
- ALuint offset = State->Offset+i;
-
- // 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 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]);
-
- // 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];
-
- // 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]);
-
- // 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];
- }
-}
-
-// Basic attenuated all-pass input/output routine.
-static inline ALfloat AllpassInOut(DelayLine *Delay, ALuint outOffset, ALuint inOffset, ALfloat in, ALfloat feedCoeff, ALfloat coeff)
-{
- ALfloat out, feed;
-
- out = DelayLineOut(Delay, outOffset);
- feed = feedCoeff * in;
- DelayLineIn(Delay, inOffset, (feedCoeff * (out - feed)) + in);
-
- // 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;
-}
-
-// All-pass input/output routine for late reverb.
-static inline ALfloat LateAllPassInOut(ALreverbState *State, ALuint offset, ALuint index, ALfloat in)
-{
- return AllpassInOut(&State->Late.ApDelay[index],
- offset - State->Late.ApOffset[index],
- offset, in, State->Late.ApFeedCoeff,
- State->Late.ApCoeff[index]);
-}
-
-// 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;
-}
-
-// 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 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];
-
- // 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]);
-
- // 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]);
-
- /* 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] ));
-
- // 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];
-
- // 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]);
- }
-}
-
-// 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])
-{
- ALfloat out, feed;
- ALuint i;
-
- for(i = 0;i < todo;i++)
- {
- 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;
-
- // Then the echo all-pass filter.
- feed = AllpassInOut(&State->Echo.ApDelay, offset-State->Echo.ApOffset,
- offset, feed, State->Echo.ApFeedCoeff,
- State->Echo.ApCoeff);
-
- // Feed the delay with the mixed and filtered sample.
- DelayLineIn(&State->Echo.Delay, offset, feed);
- }
-}
-
-// 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])
-{
- 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);
-
- // 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;
-}
-
-// 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])
-{
- ALuint i;
-
- // 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);
-
- // 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);
- }
-
- // 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);
-
- // 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)
-{
- ALfloat (*restrict early)[4] = State->EarlySamples;
- ALfloat (*restrict late)[4] = State->ReverbSamples;
- ALuint index, c, i, l;
- ALfloat gain;
-
- /* Process reverb for these samples. */
- for(index = 0;index < SamplesToDo;)
- {
- ALuint todo = minu(SamplesToDo-index, MAX_UPDATE_SAMPLES);
-
- VerbPass(State, todo, &SamplesIn[index], early, late);
-
- 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_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;
-
- /* Process reverb for these samples. */
- for(index = 0;index < SamplesToDo;)
- {
- ALuint todo = minu(SamplesToDo-index, MAX_UPDATE_SAMPLES);
-
- EAXVerbPass(State, todo, &SamplesIn[index], early, late);
-
- 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_process(ALreverbState *State, ALuint SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALuint NumChannels)
-{
- if(State->IsEax)
- ALreverbState_processEax(State, SamplesToDo, SamplesIn[0], SamplesOut, NumChannels);
- else
- ALreverbState_processStandard(State, SamplesToDo, SamplesIn[0], SamplesOut, NumChannels);
-}
+/**************************************
+ * Device Update *
+ **************************************/
// Given the allocated sample buffer, this function updates each delay line
// offset.
@@ -772,10 +377,8 @@ static ALboolean ALreverbState_deviceUpdate(ALreverbState *State, ALCdevice *Dev
// 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);
+ 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
@@ -785,6 +388,10 @@ static ALboolean ALreverbState_deviceUpdate(ALreverbState *State, ALCdevice *Dev
return AL_TRUE;
}
+/**************************************
+ * 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)
@@ -1285,15 +892,425 @@ static ALvoid ALreverbState_update(ALreverbState *State, const ALCdevice *Device
}
-static ALvoid ALreverbState_Destruct(ALreverbState *State)
+/**************************************
+ * Effect Processing *
+ **************************************/
+
+// Basic delay line input/output routines.
+static inline ALfloat DelayLineOut(DelayLine *Delay, ALuint offset)
{
- free(State->SampleBuffer);
- State->SampleBuffer = NULL;
+ return Delay->Line[offset&Delay->Mask];
}
-DECLARE_DEFAULT_ALLOCATORS(ALreverbState)
+static inline ALvoid DelayLineIn(DelayLine *Delay, ALuint offset, ALfloat in)
+{
+ Delay->Line[offset&Delay->Mask] = in;
+}
-DEFINE_ALEFFECTSTATE_VTABLE(ALreverbState);
+// Given an input sample, this function produces modulation for the late
+// reverb.
+static inline ALfloat EAXModulation(ALreverbState *State, ALuint offset, ALfloat in)
+{
+ 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);
+
+ // Step the modulation index forward, keeping it bound to its range.
+ State->Mod.Index = (State->Mod.Index + 1) % State->Mod.Range;
+
+ // 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);
+
+ // 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);
+}
+
+// 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;
+
+ for(i = 0;i < todo;i++)
+ {
+ ALuint offset = State->Offset+i;
+
+ // 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 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]);
+
+ // 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];
+
+ // 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]);
+
+ // 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];
+ }
+}
+
+// Basic attenuated all-pass input/output routine.
+static inline ALfloat AllpassInOut(DelayLine *Delay, ALuint outOffset, ALuint inOffset, ALfloat in, ALfloat feedCoeff, ALfloat coeff)
+{
+ ALfloat out, feed;
+
+ out = DelayLineOut(Delay, outOffset);
+ feed = feedCoeff * in;
+ DelayLineIn(Delay, inOffset, (feedCoeff * (out - feed)) + in);
+
+ // 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;
+}
+
+// All-pass input/output routine for late reverb.
+static inline ALfloat LateAllPassInOut(ALreverbState *State, ALuint offset, ALuint index, ALfloat in)
+{
+ return AllpassInOut(&State->Late.ApDelay[index],
+ offset - State->Late.ApOffset[index],
+ offset, in, State->Late.ApFeedCoeff,
+ State->Late.ApCoeff[index]);
+}
+
+// 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;
+}
+
+// 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 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];
+
+ // 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]);
+
+ // 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]);
+
+ /* 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] ));
+
+ // 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];
+
+ // 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]);
+ }
+}
+
+// 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])
+{
+ ALfloat out, feed;
+ ALuint i;
+
+ for(i = 0;i < todo;i++)
+ {
+ 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;
+
+ // Then the echo all-pass filter.
+ feed = AllpassInOut(&State->Echo.ApDelay, offset-State->Echo.ApOffset,
+ offset, feed, State->Echo.ApFeedCoeff,
+ State->Echo.ApCoeff);
+
+ // Feed the delay with the mixed and filtered sample.
+ DelayLineIn(&State->Echo.Delay, offset, feed);
+ }
+}
+
+// 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])
+{
+ 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);
+
+ // 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;
+}
+
+// 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])
+{
+ ALuint i;
+
+ // 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);
+
+ // 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);
+ }
+
+ // 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);
+
+ // 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)
+{
+ ALfloat (*restrict early)[4] = State->EarlySamples;
+ ALfloat (*restrict late)[4] = State->ReverbSamples;
+ ALuint index, c, i, l;
+ ALfloat gain;
+
+ /* Process reverb for these samples. */
+ for(index = 0;index < SamplesToDo;)
+ {
+ ALuint todo = minu(SamplesToDo-index, MAX_UPDATE_SAMPLES);
+
+ VerbPass(State, todo, &SamplesIn[index], early, late);
+
+ 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_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;
+
+ /* Process reverb for these samples. */
+ for(index = 0;index < SamplesToDo;)
+ {
+ ALuint todo = minu(SamplesToDo-index, MAX_UPDATE_SAMPLES);
+
+ EAXVerbPass(State, todo, &SamplesIn[index], early, late);
+
+ 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_process(ALreverbState *State, ALuint SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALuint NumChannels)
+{
+ if(State->IsEax)
+ ALreverbState_processEax(State, SamplesToDo, SamplesIn[0], SamplesOut, NumChannels);
+ else
+ ALreverbState_processStandard(State, SamplesToDo, SamplesIn[0], SamplesOut, NumChannels);
+}
typedef struct ALreverbStateFactory {