wine-staging/patches/dsound-EAX/0017-dsound-Implement-EAX-late-reverb.patch
2016-05-15 02:23:15 +02:00

373 lines
14 KiB
Diff

From 03930e42e47619af1b84a11aa21b937404e290fc Mon Sep 17 00:00:00 2001
From: Mark Harmstone <mark@harmstone.com>
Date: Sun, 22 Mar 2015 18:21:02 +0000
Subject: dsound: Implement EAX late reverb.
---
dlls/dsound/dsound_eax.h | 13 +++
dlls/dsound/eax.c | 256 ++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 268 insertions(+), 1 deletion(-)
diff --git a/dlls/dsound/dsound_eax.h b/dlls/dsound/dsound_eax.h
index c33a2ff..fac4d9b 100644
--- a/dlls/dsound/dsound_eax.h
+++ b/dlls/dsound/dsound_eax.h
@@ -154,6 +154,19 @@ typedef struct {
DelayLine Decorrelator;
unsigned int DecoTap[3];
+ struct {
+ float Gain;
+ float DensityGain;
+ float MixCoeff;
+
+ float Coeff[4];
+ DelayLine Delay[4];
+ unsigned int Offset[4];
+
+ float LpCoeff[4];
+ float LpSample[4];
+ } Late;
+
unsigned int Offset;
} eax_buffer_info;
diff --git a/dlls/dsound/eax.c b/dlls/dsound/eax.c
index d40b7d6..c9a5eb6 100644
--- a/dlls/dsound/eax.c
+++ b/dlls/dsound/eax.c
@@ -107,6 +107,8 @@ static const float LATE_LINE_LENGTH[4] =
static const float LATE_LINE_MULTIPLIER = 4.0f;
+#define SPEEDOFSOUNDMETRESPERSEC 343.3f
+
static float lpFilter2P(FILTER *iir, unsigned int offset, float input)
{
float *history = &iir->history[offset*2];
@@ -121,6 +123,11 @@ static float lpFilter2P(FILTER *iir, unsigned int offset, float input)
return output;
}
+static float lerp(float val1, float val2, float mu)
+{
+ return val1 + (val2-val1)*mu;
+}
+
static void DelayLineIn(DelayLine *Delay, unsigned int offset, float in)
{
Delay->Line[offset&Delay->Mask] = in;
@@ -187,8 +194,85 @@ static void EarlyReflection(IDirectSoundBufferImpl* dsb, float in, float *out)
out[3] = dsb->eax.Early.Gain * f[3];
}
+static float LateDelayLineOut(IDirectSoundBufferImpl* dsb, unsigned int index)
+{
+ return AttenuatedDelayLineOut(&dsb->eax.Late.Delay[index],
+ dsb->eax.Offset - dsb->eax.Late.Offset[index],
+ dsb->eax.Late.Coeff[index]);
+}
+
+static float LateLowPassInOut(IDirectSoundBufferImpl* dsb, unsigned int index, float in)
+{
+ in = lerp(in, dsb->eax.Late.LpSample[index], dsb->eax.Late.LpCoeff[index]);
+ dsb->eax.Late.LpSample[index] = in;
+ return in;
+}
+
+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));
+
+ /* 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]);
+}
+
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);
@@ -199,6 +283,25 @@ static void VerbPass(IDirectSoundBufferImpl* dsb, float in, float* out)
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++;
}
@@ -308,6 +411,95 @@ static float CalcI3DL2HFreq(float hfRef, unsigned int frequency)
return cosf(M_PI*2.0f * hfRef / frequency);
}
+static float CalcDensityGain(float a)
+{
+ return sqrtf(1.0f - (a * a));
+}
+
+static float CalcDampingCoeff(float hfRatio, float length, float decayTime, float decayCoeff, float cw)
+{
+ float coeff, g;
+
+ coeff = 0.0f;
+ if (hfRatio < 1.0f)
+ {
+ /* Calculate the low-pass coefficient by dividing the HF decay
+ * coefficient by the full decay coefficient. */
+ g = CalcDecayCoeff(length, decayTime * hfRatio) / decayCoeff;
+
+ /* Damping is done with a 1-pole filter, so g needs to be squared. */
+ g *= g;
+ coeff = lpCoeffCalc(g, cw);
+
+ /* Very low decay times will produce minimal output, so apply an
+ * upper bound to the coefficient. */
+ if (coeff > 0.98f) coeff = 0.98f;
+ }
+ return coeff;
+}
+
+static void UpdateLateLines(float reverbGain, float lateGain, float xMix, float density, float decayTime,
+ float diffusion, float hfRatio, float cw, unsigned int frequency, eax_buffer_info *State)
+{
+ float length;
+ unsigned int index;
+
+ /* Calculate the late reverb gain (from the master effect gain, and late
+ * reverb gain parameters). Since the output is tapped prior to the
+ * application of the next delay line coefficients, this gain needs to be
+ * attenuated by the 'x' mixing matrix coefficient as well.
+ */
+ State->Late.Gain = reverbGain * lateGain * xMix;
+
+ /* To compensate for changes in modal density and decay time of the late
+ * reverb signal, the input is attenuated based on the maximal energy of
+ * the outgoing signal. This approximation is used to keep the apparent
+ * energy of the signal equal for all ranges of density and decay time.
+ *
+ * The average length of the cyclcical delay lines is used to calculate
+ * the attenuation coefficient.
+ */
+ length = (LATE_LINE_LENGTH[0] + LATE_LINE_LENGTH[1] +
+ LATE_LINE_LENGTH[2] + LATE_LINE_LENGTH[3]) / 4.0f;
+ length *= 1.0f + (density * LATE_LINE_MULTIPLIER);
+ State->Late.DensityGain = CalcDensityGain(CalcDecayCoeff(length,
+ decayTime));
+
+ for(index = 0; index < 4; index++)
+ {
+ /* Calculate the length (in seconds) of each cyclical delay line. */
+ length = LATE_LINE_LENGTH[index] * (1.0f + (density * LATE_LINE_MULTIPLIER));
+
+ /* Calculate the delay offset for each cyclical delay line. */
+ State->Late.Offset[index] = fastf2u(length * frequency);
+
+ /* Calculate the gain (coefficient) for each cyclical line. */
+ State->Late.Coeff[index] = CalcDecayCoeff(length, decayTime);
+
+ /* Calculate the damping coefficient for each low-pass filter. */
+ State->Late.LpCoeff[index] = CalcDampingCoeff(hfRatio, length, decayTime,
+ State->Late.Coeff[index], cw);
+
+ /* Attenuate the cyclical line coefficients by the mixing coefficient
+ * (x). */
+ State->Late.Coeff[index] *= xMix;
+ }
+}
+
+static void CalcMatrixCoeffs(float diffusion, float *x, float *y)
+{
+ float n, t;
+
+ /* The matrix is of order 4, so n is sqrt (4 - 1). */
+ n = sqrtf(3.0f);
+ t = diffusion * atanf(n);
+
+ /* Calculate the first mixing matrix coefficient. */
+ *x = cosf(t);
+ /* Calculate the second mixing matrix coefficient. */
+ *y = sinf(t) / n;
+}
+
static unsigned int NextPowerOf2(unsigned int value)
{
if (value > 0)
@@ -370,6 +562,14 @@ static BOOL AllocLines(unsigned int frequency, IDirectSoundBufferImpl *dsb)
totalSamples += CalcLineLength(length, totalSamples, frequency,
&dsb->eax.Decorrelator);
+ /* The late delay lines are calculated from the lowest reverb density. */
+ for (index = 0; index < 4; index++)
+ {
+ length = LATE_LINE_LENGTH[index] * (1.0f + LATE_LINE_MULTIPLIER);
+ totalSamples += CalcLineLength(length, totalSamples, frequency,
+ &dsb->eax.Late.Delay[index]);
+ }
+
if (totalSamples != dsb->eax.TotalSamples)
{
TRACE("New reverb buffer length: %u samples (%f sec)\n", totalSamples, totalSamples/(float)frequency);
@@ -391,6 +591,7 @@ static BOOL AllocLines(unsigned int frequency, IDirectSoundBufferImpl *dsb)
for(index = 0; index < 4; index++)
{
RealizeLineOffset(dsb->eax.SampleBuffer, &dsb->eax.Early.Delay[index]);
+ RealizeLineOffset(dsb->eax.SampleBuffer, &dsb->eax.Late.Delay[index]);
}
/* Clear the sample buffer. */
@@ -400,10 +601,35 @@ static BOOL AllocLines(unsigned int frequency, IDirectSoundBufferImpl *dsb)
return TRUE;
}
+static inline float CalcDecayLength(float coeff, float decayTime)
+{
+ return log10f(coeff) * decayTime / log10f(0.001f)/*-60 dB*/;
+}
+
+static float CalcLimitedHfRatio(float hfRatio, float airAbsorptionGainHF, float decayTime)
+{
+ float limitRatio;
+
+ /* Find the attenuation due to air absorption in dB (converting delay
+ * time to meters using the speed of sound). Then reversing the decay
+ * equation, solve for HF ratio. The delay length is cancelled out of
+ * the equation, so it can be calculated once for all lines.
+ */
+ limitRatio = 1.0f / (CalcDecayLength(airAbsorptionGainHF, decayTime) *
+ SPEEDOFSOUNDMETRESPERSEC);
+ /* Using the limit calculated above, apply the upper bound to the HF
+ * ratio. Also need to limit the result to a minimum of 0.1, just like the
+ * HF ratio parameter. */
+ if (limitRatio < 0.1f) limitRatio = 0.1f;
+ else if (limitRatio > hfRatio) limitRatio = hfRatio;
+
+ return limitRatio;
+}
+
static void ReverbUpdate(IDirectSoundBufferImpl *dsb)
{
unsigned int index;
- float cw;
+ float cw, hfRatio, x, y;
/* avoid segfaults in mixing thread when we recalculate the line offsets */
EnterCriticalSection(&dsb->device->mixlock);
@@ -430,6 +656,20 @@ static void ReverbUpdate(IDirectSoundBufferImpl *dsb)
dsb->device->eax.eax_props.flLateReverbDelay, &dsb->eax);
UpdateDecorrelator(dsb->device->eax.eax_props.flDensity, dsb->device->pwfx->nSamplesPerSec, &dsb->eax);
+
+ CalcMatrixCoeffs(dsb->device->eax.eax_props.flDiffusion, &x, &y);
+ dsb->eax.Late.MixCoeff = y / x;
+
+ hfRatio = dsb->device->eax.eax_props.flDecayHFRatio;
+
+ if (dsb->device->eax.eax_props.iDecayHFLimit && dsb->device->eax.eax_props.flAirAbsorptionGainHF < 1.0f) {
+ hfRatio = CalcLimitedHfRatio(hfRatio, dsb->device->eax.eax_props.flAirAbsorptionGainHF,
+ dsb->device->eax.eax_props.flDecayTime);
+ }
+
+ UpdateLateLines(dsb->device->eax.eax_props.flGain, dsb->device->eax.eax_props.flLateReverbGain,
+ x, dsb->device->eax.eax_props.flDensity, dsb->device->eax.eax_props.flDecayTime,
+ dsb->device->eax.eax_props.flDiffusion, hfRatio, cw, dsb->device->pwfx->nSamplesPerSec, &dsb->eax);
}
static BOOL ReverbDeviceUpdate(DirectSoundDevice *dev)
@@ -474,6 +714,20 @@ void init_eax_buffer(IDirectSoundBufferImpl *dsb)
dsb->eax.DecoTap[1] = 0;
dsb->eax.DecoTap[2] = 0;
+ dsb->eax.Late.Gain = 0.0f;
+ dsb->eax.Late.DensityGain = 0.0f;
+ dsb->eax.Late.MixCoeff = 0.0f;
+ for(index = 0; index < 4; index++)
+ {
+ dsb->eax.Late.Coeff[index] = 0.0f;
+ dsb->eax.Late.Delay[index].Mask = 0;
+ dsb->eax.Late.Delay[index].Line = NULL;
+ dsb->eax.Late.Offset[index] = 0;
+
+ dsb->eax.Late.LpCoeff[index] = 0.0f;
+ dsb->eax.Late.LpSample[index] = 0.0f;
+ }
+
dsb->eax.Offset = 0;
ReverbUpdate(dsb);
--
2.3.3