// Perform the non-EAX reverb pass on a given input sample, resulting in // four-channel output. static __inline ALvoid VerbPass(ALverbState *State, ALfloat in, ALfloat *early, ALfloat *late) { ALfloat feed, taps[4]; // Low-pass filter the incoming sample. in = lpFilter2P(&State->LpFilter, 0, in); // Feed the initial delay line. DelayLineIn(&State->Delay, State->Offset, in); // Calculate the early reflection from the first delay tap. in = DelayLineOut(&State->Delay, State->Offset - State->DelayTap[0]); EarlyReflection(State, in, early); // Feed the decorrelator from the energy-attenuated output of the second // delay tap. in = DelayLineOut(&State->Delay, State->Offset - State->DelayTap[1]); feed = in * State->Late.DensityGain; DelayLineIn(&State->Decorrelator, State->Offset, feed); // Calculate the late reverb from the decorrelator taps. taps[0] = feed; taps[1] = DelayLineOut(&State->Decorrelator, State->Offset - State->DecoTap[0]); taps[2] = DelayLineOut(&State->Decorrelator, State->Offset - State->DecoTap[1]); taps[3] = DelayLineOut(&State->Decorrelator, State->Offset - State->DecoTap[2]); LateReverb(State, taps, late); // Step all delays forward one sample. State->Offset++; }
// Given an input sample, this function produces modulation for the late // reverb. static __inline ALfloat EAXModulation(ALverbState *State, ALfloat in) { ALfloat sinus, frac; ALuint offset; ALfloat out0, out1; // 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_PI*2.0f * State->Mod.Index / 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 = (1.0f + (State->Mod.Filter * sinus)); offset = fastf2u(frac); frac -= offset; // Get the two samples crossed by the offset, and feed the delay line // with the next input sample. out0 = DelayLineOut(&State->Mod.Delay, State->Offset - offset); out1 = DelayLineOut(&State->Mod.Delay, State->Offset - offset - 1); DelayLineIn(&State->Mod.Delay, State->Offset, in); // Step the modulation index forward, keeping it bound to its range. State->Mod.Index = (State->Mod.Index + 1) % State->Mod.Range; // The output is obtained by linearly interpolating the two samples that // were acquired above. return lerp(out0, out1, frac); }
// Given an input sample, this function mixes echo into the four-channel late // reverb. static __inline ALvoid EAXEcho(ALverbState *State, ALfloat in, ALfloat *late) { ALfloat out, feed; // Get the latest attenuated echo sample for output. feed = AttenuatedDelayLineOut(&State->Echo.Delay, State->Offset - State->Echo.Offset, State->Echo.Coeff); // Mix the output into the late reverb channels. out = State->Echo.MixCoeff[0] * feed; late[0] = (State->Echo.MixCoeff[1] * late[0]) + out; late[1] = (State->Echo.MixCoeff[1] * late[1]) + out; late[2] = (State->Echo.MixCoeff[1] * late[2]) + out; late[3] = (State->Echo.MixCoeff[1] * late[3]) + out; // Mix the energy-attenuated input with the output and pass it through // the echo low-pass filter. feed += State->Echo.DensityGain * in; feed = lerp(feed, State->Echo.LpSample, State->Echo.LpCoeff); State->Echo.LpSample = feed; // Then the echo all-pass filter. feed = AllpassInOut(&State->Echo.ApDelay, State->Offset - State->Echo.ApOffset, State->Offset, feed, State->Echo.ApFeedCoeff, State->Echo.ApCoeff); // Feed the delay with the mixed and filtered sample. DelayLineIn(&State->Echo.Delay, State->Offset, feed); }
static void EarlyReflection(IDirectSoundBufferImpl* dsb, float in, float *out) { float d[4], v, f[4]; /* Obtain the decayed results of each early delay line. */ d[0] = EarlyDelayLineOut(dsb, 0); d[1] = EarlyDelayLineOut(dsb, 1); d[2] = EarlyDelayLineOut(dsb, 2); d[3] = EarlyDelayLineOut(dsb, 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 += in; /* Calculate the feed values for the delay lines. */ f[0] = v - d[0]; f[1] = v - d[1]; f[2] = v - d[2]; f[3] = v - d[3]; /* Re-feed the delay lines. */ DelayLineIn(&dsb->eax.Early.Delay[0], dsb->eax.Offset, f[0]); DelayLineIn(&dsb->eax.Early.Delay[1], dsb->eax.Offset, f[1]); DelayLineIn(&dsb->eax.Early.Delay[2], dsb->eax.Offset, f[2]); DelayLineIn(&dsb->eax.Early.Delay[3], dsb->eax.Offset, f[3]); /* Output the results of the junction for all four channels. */ out[0] = dsb->eax.Early.Gain * f[0]; out[1] = dsb->eax.Early.Gain * f[1]; out[2] = dsb->eax.Early.Gain * f[2]; out[3] = dsb->eax.Early.Gain * f[3]; }
// Given an input sample, this function produces four-channel output for the // early reflections. static __inline ALvoid EarlyReflection(ALverbState *State, ALfloat in, ALfloat *out) { ALfloat d[4], v, f[4]; // Obtain the decayed results of each early delay line. d[0] = EarlyDelayLineOut(State, 0); d[1] = EarlyDelayLineOut(State, 1); d[2] = EarlyDelayLineOut(State, 2); d[3] = EarlyDelayLineOut(State, 3); /* The following uses a lossless scattering junction from waveguide * theory. It actually amounts to a householder mixing matrix, which * will produce a maximally diffuse response, and means this can probably * be considered a simple 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 += in; // Calculate the feed values for the delay lines. f[0] = v - d[0]; f[1] = v - d[1]; f[2] = v - d[2]; f[3] = v - d[3]; // Re-feed the delay lines. DelayLineIn(&State->Early.Delay[0], State->Offset, f[0]); DelayLineIn(&State->Early.Delay[1], State->Offset, f[1]); DelayLineIn(&State->Early.Delay[2], State->Offset, f[2]); DelayLineIn(&State->Early.Delay[3], State->Offset, f[3]); // Output the results of the junction for all four channels. out[0] = State->Early.Gain * f[0]; out[1] = State->Early.Gain * f[1]; out[2] = State->Early.Gain * f[2]; out[3] = State->Early.Gain * f[3]; }
static float AllpassInOut(DelayLine *Delay, unsigned int outOffset, unsigned int inOffset, float in, float feedCoeff, float coeff) { float 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; }
// 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; }
static void VerbPass(IDirectSoundBufferImpl* dsb, float in, float* out) { float feed, late[4], taps[4]; /* Low-pass filter the incoming sample. */ in = lpFilter2P(&dsb->eax.LpFilter, 0, in); /* Feed the initial delay line. */ DelayLineIn(&dsb->eax.Delay, dsb->eax.Offset, in); /* Calculate the early reflection from the first delay tap. */ in = DelayLineOut(&dsb->eax.Delay, dsb->eax.Offset - dsb->eax.DelayTap[0]); EarlyReflection(dsb, in, out); /* Feed the decorrelator from the energy-attenuated output of the second * delay tap. */ in = DelayLineOut(&dsb->eax.Delay, dsb->eax.Offset - dsb->eax.DelayTap[1]); feed = in * dsb->eax.Late.DensityGain; DelayLineIn(&dsb->eax.Decorrelator, dsb->eax.Offset, feed); /* Calculate the late reverb from the decorrelator taps. */ taps[0] = feed; taps[1] = DelayLineOut(&dsb->eax.Decorrelator, dsb->eax.Offset - dsb->eax.DecoTap[0]); taps[2] = DelayLineOut(&dsb->eax.Decorrelator, dsb->eax.Offset - dsb->eax.DecoTap[1]); taps[3] = DelayLineOut(&dsb->eax.Decorrelator, dsb->eax.Offset - dsb->eax.DecoTap[2]); LateReverb(dsb, taps, late); /* Mix early reflections and late reverb. */ out[0] += late[0]; out[1] += late[1]; out[2] += late[2]; out[3] += late[3]; /* Step all delays forward one sample. */ dsb->eax.Offset++; }
static void LateReverb(IDirectSoundBufferImpl* dsb, const float *in, float *out) { float d[4], f[4]; /* Obtain the decayed results of the cyclical delay lines, and add the * corresponding input channels. Then pass the results through the * low-pass filters. */ /* This is where the feed-back cycles from line 0 to 1 to 3 to 2 and back * to 0. */ d[0] = LateLowPassInOut(dsb, 2, in[2] + LateDelayLineOut(dsb, 2)); d[1] = LateLowPassInOut(dsb, 0, in[0] + LateDelayLineOut(dsb, 0)); d[2] = LateLowPassInOut(dsb, 3, in[3] + LateDelayLineOut(dsb, 3)); d[3] = LateLowPassInOut(dsb, 1, in[1] + LateDelayLineOut(dsb, 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(dsb, 0, d[0]); d[1] = LateAllPassInOut(dsb, 1, d[1]); d[2] = LateAllPassInOut(dsb, 2, d[2]); d[3] = LateAllPassInOut(dsb, 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] + (dsb->eax.Late.MixCoeff * ( d[1] + -d[2] + d[3])); f[1] = d[1] + (dsb->eax.Late.MixCoeff * (-d[0] + d[2] + d[3])); f[2] = d[2] + (dsb->eax.Late.MixCoeff * ( d[0] + -d[1] + d[3])); f[3] = d[3] + (dsb->eax.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). */ out[0] = dsb->eax.Late.Gain * f[0]; out[1] = dsb->eax.Late.Gain * f[1]; out[2] = dsb->eax.Late.Gain * f[2]; out[3] = dsb->eax.Late.Gain * f[3]; /* Re-feed the cyclical delay lines. */ DelayLineIn(&dsb->eax.Late.Delay[0], dsb->eax.Offset, f[0]); DelayLineIn(&dsb->eax.Late.Delay[1], dsb->eax.Offset, f[1]); DelayLineIn(&dsb->eax.Late.Delay[2], dsb->eax.Offset, f[2]); DelayLineIn(&dsb->eax.Late.Delay[3], dsb->eax.Offset, f[3]); }