From 87d4710bc4f980663fa8ca67a9ef416aa56b2a93 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Fri, 12 May 2017 20:53:14 -0700 Subject: Apply reverb modulation to the late feedback lines This seems to be more in-line with the intended behavior, to allow build-up and overlap within the reverb decay, rather than a pitch-shift on input. Unfortunately there's no readily available implementation of this reverb model that includes modulation to compare with, so a low depth coefficient is used to keep it very subtle. --- Alc/effects/reverb.c | 175 +++++++++++++++++++++------------------------------ 1 file changed, 71 insertions(+), 104 deletions(-) diff --git a/Alc/effects/reverb.c b/Alc/effects/reverb.c index 07a1b679..c1b0369b 100644 --- a/Alc/effects/reverb.c +++ b/Alc/effects/reverb.c @@ -89,22 +89,6 @@ typedef struct ALreverbState { ALfilterState Hp; /* EAX only */ } Filter[4]; - struct { - /* Modulator delay lines. */ - DelayLine Delay[4]; - - /* The vibrato time is tracked with an index over a modulus-wrapped - * range (in samples). - */ - ALuint Index; - ALuint Range; - - /* The depth of frequency change (also in samples) and its filter. */ - ALfloat Depth; - ALfloat Coeff; - ALfloat Filter; - } Mod; /* EAX only */ - /* Core delay line (early reflections and late reverb tap from this). */ DelayLine Delay; @@ -142,6 +126,19 @@ typedef struct ALreverbState { ALfloat PanGain[4][MAX_OUTPUT_CHANNELS]; } Early; + struct { + /* The vibrato time is tracked with an index over a modulus-wrapped + * range (in samples). + */ + ALuint Index; + ALuint Range; + + /* The depth of frequency change (also in samples) and its filter. */ + ALfloat Depth; + ALfloat Coeff; + ALfloat Filter; + } Mod; /* EAX only */ + struct { /* Attenuation to compensate for the modal density and decay rate of * the late lines. @@ -207,17 +204,8 @@ static void ALreverbState_Construct(ALreverbState *state) { ALfilterState_clear(&state->Filter[i].Lp); ALfilterState_clear(&state->Filter[i].Hp); - - state->Mod.Delay[i].Mask = 0; - state->Mod.Delay[i].Line = NULL; } - state->Mod.Index = 0; - state->Mod.Range = 1; - state->Mod.Depth = 0.0f; - state->Mod.Coeff = 0.0f; - state->Mod.Filter = 0.0f; - state->Delay.Mask = 0; state->Delay.Line = NULL; @@ -253,6 +241,12 @@ static void ALreverbState_Construct(ALreverbState *state) state->Early.Coeff[i] = 0.0f; } + state->Mod.Index = 0; + state->Mod.Range = 1; + state->Mod.Depth = 0.0f; + state->Mod.Coeff = 0.0f; + state->Mod.Filter = 0.0f; + state->Late.DensityGain = 0.0f; for(i = 0;i < 4;i++) @@ -337,13 +331,12 @@ ALfloat ReverbBoost = 1.0f; */ ALboolean EmulateEAXReverb = AL_FALSE; -/* This coefficient is used to define the maximum frequency range controlled - * by the modulation depth. The current value of 0.025 will allow it to - * swing from 0.975x to 1.025x. This value must be below 1. At 1 it will - * cause the sampler to stall on the downswing, and above 1 it will cause it - * to sample backwards. +/* This coefficient is used to define the sinus depth according to the + * modulation depth property. This value must be below 1, which would cause the + * sampler to stall on the downswing, and above 1 it will cause it to sample + * backwards. */ -static const ALfloat MODULATION_DEPTH_COEFF = 0.025f; +static const ALfloat MODULATION_DEPTH_COEFF = 1.0f / 2048.0f; /* A filter is used to avoid the terrible distortion caused by changing * modulation time and/or depth. To be consistent across different sample @@ -540,16 +533,6 @@ static ALboolean AllocLines(const ALuint frequency, ALreverbState *State) */ totalSamples = 0; - /* The modulator's line length is calculated from the maximum modulation - * time and depth coefficient, and halfed for the low-to-high frequency - * swing. An additional sample is added to keep it stable when there is no - * modulation. - */ - length = (AL_EAXREVERB_MAX_MODULATION_TIME*MODULATION_DEPTH_COEFF/2.0f); - for(i = 0;i < 4;i++) - totalSamples += CalcLineLength(length, totalSamples, frequency, 1, - &State->Mod.Delay[i]); - /* The main delay length includes the maximum early reflection delay, the * largest early tap width, the maximum late reverb delay, and the * largest late tap width. Finally, it must also be extended by the @@ -591,11 +574,15 @@ static ALboolean AllocLines(const ALuint frequency, ALreverbState *State) } /* The late delay lines are calculated from the larger of the maximum - * density line length or the maximum echo time. + * density line length or the maximum echo time, and includes the maximum + * modulation-related delay. The modulator's delay is calculated from the + * maximum modulation time and depth coefficient, and halved for the low- + * to-high frequency swing. */ for(i = 0;i < 4;i++) { - length = maxf(AL_EAXREVERB_MAX_ECHO_TIME, LATE_LINE_LENGTHS[i] * multiplier); + length = maxf(AL_EAXREVERB_MAX_ECHO_TIME, LATE_LINE_LENGTHS[i]*multiplier) + + AL_EAXREVERB_MAX_MODULATION_TIME*MODULATION_DEPTH_COEFF/2.0f; totalSamples += CalcLineLength(length, totalSamples, frequency, 0, &State->Late.Delay[i]); } @@ -617,8 +604,6 @@ static ALboolean AllocLines(const ALuint frequency, ALreverbState *State) RealizeLineOffset(State->SampleBuffer, &State->Delay); for(i = 0;i < 4;i++) { - RealizeLineOffset(State->SampleBuffer, &State->Mod.Delay[i]); - RealizeLineOffset(State->SampleBuffer, &State->Early.Ap[i].Delay); RealizeLineOffset(State->SampleBuffer, &State->Early.Delay[i]); @@ -1069,7 +1054,8 @@ static void CalcT60DampingCoeffs(const ALfloat length, const ALfloat lfDecayTime * kind of vibrato is additive and not multiplicative as one may expect. The * downswing will sound stronger than the upswing. */ -static ALvoid UpdateModulator(const ALfloat modTime, const ALfloat modDepth, const ALuint frequency, ALreverbState *State) +static ALvoid UpdateModulator(const ALfloat modTime, const ALfloat modDepth, + const ALuint frequency, ALreverbState *State) { ALuint range; @@ -1372,10 +1358,6 @@ static ALvoid ALreverbState_update(ALreverbState *State, const ALCdevice *Device State->Filter[i].Hp.a2 = State->Filter[0].Hp.a2; } - /* Update the modulator line. */ - UpdateModulator(props->Reverb.ModulationTime, props->Reverb.ModulationDepth, - frequency, State); - /* Update the main effect delay and associated taps. */ UpdateDelayLine(props->Reverb.ReflectionsDelay, props->Reverb.LateReverbDelay, props->Reverb.Density, props->Reverb.DecayTime, frequency, @@ -1405,6 +1387,10 @@ static ALvoid ALreverbState_update(ALreverbState *State, const ALCdevice *Device hfDecayTime = clampf(props->Reverb.DecayTime * hfRatio, AL_EAXREVERB_MIN_DECAY_TIME, AL_EAXREVERB_MAX_DECAY_TIME); + /* Update the modulator line. */ + UpdateModulator(props->Reverb.ModulationTime, props->Reverb.ModulationDepth, + frequency, State); + /* Update the late lines. */ UpdateLateLines(props->Reverb.Density, props->Reverb.Diffusion, lfDecayTime, props->Reverb.DecayTime, hfDecayTime, @@ -1441,7 +1427,7 @@ static ALvoid ALreverbState_update(ALreverbState *State, const ALCdevice *Device **************************************/ /* Basic delay line input/output routines. */ -static inline ALfloat DelayLineOut(DelayLine *Delay, const ALsizei offset) +static inline ALfloat DelayLineOut(const DelayLine *Delay, const ALsizei offset) { return Delay->Line[offset&Delay->Mask]; } @@ -1449,7 +1435,7 @@ static inline ALfloat DelayLineOut(DelayLine *Delay, const ALsizei offset) /* Cross-faded delay line output routine. Instead of interpolating the * offsets, this interpolates (cross-fades) the outputs at each offset. */ -static inline ALfloat FadedDelayLineOut(DelayLine *Delay, const ALsizei off0, const ALsizei off1, const ALfloat mu) +static inline ALfloat FadedDelayLineOut(const DelayLine *Delay, const ALsizei off0, const ALsizei off1, const ALfloat mu) { return lerp(Delay->Line[off0&Delay->Mask], Delay->Line[off1&Delay->Mask], mu); } @@ -1461,12 +1447,6 @@ static inline ALvoid DelayLineIn(DelayLine *Delay, const ALsizei offset, const A Delay->Line[offset&Delay->Mask] = in; } -static inline ALfloat DelayLineInOut(DelayLine *Delay, const ALsizei offset, const ALsizei outoffset, const ALfloat in) -{ - Delay->Line[offset&Delay->Mask] = in; - return Delay->Line[(offset-outoffset)&Delay->Mask]; -} - static void CalcModulationDelays(ALreverbState *State, ALfloat *restrict delays, const ALsizei todo) { ALfloat sinus, range; @@ -1498,37 +1478,6 @@ static void CalcModulationDelays(ALreverbState *State, ALfloat *restrict delays, State->Mod.Filter = range; } -/* Given some input samples, this function produces modulation for the late - * reverb. - */ -static void EAXModulation(DelayLine *ModDelay, ALsizei offset, const ALfloat *restrict delays, ALfloat*restrict dst, const ALfloat*restrict src, const ALsizei todo) -{ - ALfloat frac, fdelay; - ALfloat out0, out1; - ALsizei delay, i; - - for(i = 0;i < todo;i++) - { - /* Separate the integer offset and fraction between it and the next - * sample. - */ - frac = modff(delays[i], &fdelay); - delay = fastf2u(fdelay); - - /* Add the incoming sample to the delay line, and get the two samples - * crossed by the offset delay. - */ - out0 = DelayLineInOut(ModDelay, offset, delay, src[i]); - out1 = DelayLineOut(ModDelay, offset - delay - 1); - offset++; - - /* The output is obtained by linearly interpolating the two samples - * that were acquired above. - */ - dst[i] = lerp(out0, out1, frac); - } -} - /* Applies a scattering matrix to the 4-line (vector) input. This is used * for both the below vector all-pass model and to perform modal feed-back * delay network (FDN) mixing. @@ -1742,10 +1691,15 @@ static ALvoid LateReverb_##T(ALreverbState *State, const ALsizei todo, \ const ALfloat apFeedCoeff = State->ApFeedCoeff; \ const ALfloat mixX = State->MixX; \ const ALfloat mixY = State->MixY; \ + ALfloat fdelay, frac; \ + ALsizei delay; \ ALsizei offset; \ ALsizei i, j; \ ALfloat f[4]; \ \ + /* Calculations modulation delays, uing the output as temp storage. */ \ + CalcModulationDelays(State, &out[0][0], todo); \ + \ offset = State->Offset; \ for(i = 0;i < todo;i++) \ { \ @@ -1755,10 +1709,29 @@ static ALvoid LateReverb_##T(ALreverbState *State, const ALsizei todo, \ (offset-State->LateDelayTap[j][1])*4 + j, fade \ ) * State->Late.DensityGain; \ \ + /* Separate the integer offset and fraction between it and the next \ + * sample. \ + */ \ + frac = modff(out[0][i], &fdelay); \ + delay = offset - fastf2i(fdelay); \ + \ for(j = 0;j < 4;j++) \ - f[j] += DELAY_OUT_##T(&State->Late.Delay[j], \ - offset-State->Late.Offset[j][0], \ - offset-State->Late.Offset[j][1], fade); \ + { \ + ALfloat out0, out1; \ + \ + /* Get the two samples crossed by the offset delay. */ \ + out0 = DELAY_OUT_##T(&State->Late.Delay[j], \ + delay-State->Late.Offset[j][0], \ + delay-State->Late.Offset[j][1], fade); \ + out1 = DELAY_OUT_##T(&State->Late.Delay[j], \ + delay-State->Late.Offset[j][0]-1, \ + delay-State->Late.Offset[j][1]-1, fade); \ + \ + /* The modulated result is obtained by linearly interpolating the \ + * two samples that were acquired above. \ + */ \ + f[j] += lerp(out0, out1, frac); \ + } \ \ for(j = 0;j < 4;j++) \ f[j] = LateT60Filter(j, f[j], State); \ @@ -1844,23 +1817,17 @@ static ALfloat EAXVerbPass(ALreverbState *State, const ALsizei todo, ALfloat fad { ALsizei i, c; - /* Perform any modulation on the input (use the early and late buffers as - * temp storage). - */ - CalcModulationDelays(State, &late[0][0], todo); for(c = 0;c < 4;c++) { - /* Apply modulation. */ - EAXModulation(&State->Mod.Delay[c], State->Offset, &late[0][0], - &early[0][0], input[c], todo); - - /* Band-pass the incoming samples. */ - ALfilterState_process(&State->Filter[c].Lp, &early[1][0], &early[0][0], todo); - ALfilterState_process(&State->Filter[c].Hp, &early[2][0], &early[1][0], todo); + /* Band-pass the incoming samples. Use the early output lines for temp + * storage. + */ + ALfilterState_process(&State->Filter[c].Lp, early[0], input[c], todo); + ALfilterState_process(&State->Filter[c].Hp, early[1], early[0], todo); /* Feed the initial delay line. */ for(i = 0;i < todo;i++) - DelayLineIn(&State->Delay, (State->Offset+i)*4 + c, early[2][i]); + DelayLineIn(&State->Delay, (State->Offset+i)*4 + c, early[1][i]); } if(fade < 1.0f) -- cgit v1.2.3