aboutsummaryrefslogtreecommitdiffstats
path: root/alc
diff options
context:
space:
mode:
authorChris Robinson <[email protected]>2019-08-24 16:25:56 -0700
committerChris Robinson <[email protected]>2019-08-24 16:25:56 -0700
commite165cae3cddec51332660bdb1e69d7fa5f687e68 (patch)
tree61104f9c7bb134e78a0219ef8b604b5632cc84c3 /alc
parent02d38091c885a5fb7c30dda5193752544e985a09 (diff)
Fade reverb over the whole update
Since the early and late panning gains fade over the course of the update, it should match the fading done by the feedback loops to avoid percussive "blasts" when transitioning to a long-decay low-gain environment from a short-decay high-gain environment.
Diffstat (limited to 'alc')
-rw-r--r--alc/effects/reverb.cpp210
1 files changed, 104 insertions, 106 deletions
diff --git a/alc/effects/reverb.cpp b/alc/effects/reverb.cpp
index ce81dfbd..5d9dbf81 100644
--- a/alc/effects/reverb.cpp
+++ b/alc/effects/reverb.cpp
@@ -53,13 +53,6 @@ using namespace std::placeholders;
*/
constexpr size_t MAX_UPDATE_SAMPLES{256};
-/* The number of samples used for cross-faded delay lines. This can be used
- * to balance the compensation for abrupt line changes and attenuation due to
- * minimally lengthed recursive lines. Try to keep this below the device
- * update size.
- */
-constexpr size_t FADE_SAMPLES{128};
-
/* The number of spatialized lines or channels to process. Four channels allows
* for a 3D A-Format response. NOTE: This can't be changed without taking care
* of the conversion matrices, and a few places where the length arrays are
@@ -91,8 +84,6 @@ alignas(16) constexpr ALfloat A2B[NUM_LINES][NUM_LINES]{
};
-constexpr ALfloat FadeStep{1.0f / FADE_SAMPLES};
-
/* The all-pass and delay lines have a variable length dependent on the
* effect's density parameter, which helps alter the perceived environment
* size. The size-to-density conversion is a cubed scale:
@@ -277,7 +268,8 @@ struct VecAllpass {
size_t Offset[NUM_LINES][2]{};
void processFaded(const al::span<ReverbUpdateLine,NUM_LINES> samples, size_t offset,
- const ALfloat xCoeff, const ALfloat yCoeff, ALfloat fade, const size_t todo);
+ const ALfloat xCoeff, const ALfloat yCoeff, ALfloat fadeCount, const ALfloat fadeStep,
+ const size_t todo);
void processUnfaded(const al::span<ReverbUpdateLine,NUM_LINES> samples, size_t offset,
const ALfloat xCoeff, const ALfloat yCoeff, const size_t todo);
};
@@ -390,8 +382,7 @@ struct ReverbState final : public EffectState {
LateReverb mLate;
- /* Indicates the cross-fade point for delay line reads [0,FADE_SAMPLES]. */
- size_t mFadeCount{0};
+ bool mDoFading{};
/* Maximum number of samples to process at once. */
size_t mMaxUpdate[2]{MAX_UPDATE_SAMPLES, MAX_UPDATE_SAMPLES};
@@ -482,11 +473,13 @@ struct ReverbState final : public EffectState {
void update3DPanning(const ALfloat *ReflectionsPan, const ALfloat *LateReverbPan,
const ALfloat earlyGain, const ALfloat lateGain, const EffectTarget &target);
- void earlyUnfaded(const size_t offset, const size_t todo, const size_t base);
- void earlyFaded(const size_t offset, const size_t todo, const ALfloat fade);
+ void earlyUnfaded(const size_t offset, const size_t todo);
+ void earlyFaded(const size_t offset, const size_t todo, const ALfloat fade,
+ const ALfloat fadeStep);
- void lateUnfaded(const size_t offset, const size_t todo, const size_t base);
- void lateFaded(const size_t offset, const size_t todo, const ALfloat fade);
+ void lateUnfaded(const size_t offset, const size_t todo);
+ void lateFaded(const size_t offset, const size_t todo, const ALfloat fade,
+ const ALfloat fadeStep);
ALboolean deviceUpdate(const ALCdevice *device) override;
void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
@@ -612,8 +605,8 @@ ALboolean ReverbState::deviceUpdate(const ALCdevice *device)
for(auto &gains : mLate.PanGain)
std::fill(std::begin(gains), std::end(gains), 0.0f);
- /* Reset counters and offset base. */
- mFadeCount = 0;
+ /* Reset fading and offset base. */
+ mDoFading = true;
std::fill(std::begin(mMaxUpdate), std::end(mMaxUpdate), MAX_UPDATE_SAMPLES);
mOffset = 0;
@@ -977,7 +970,7 @@ void ReverbState::update(const ALCcontext *Context, const ALeffectslot *Slot, co
* a master control for the feedback delays, so changes the offsets of many
* delay lines.
*/
- if(mParams.Density != props->Reverb.Density ||
+ mDoFading |= (mParams.Density != props->Reverb.Density ||
/* Diffusion and decay times influences the decay rate (gain) of the
* late reverb T60 filter.
*/
@@ -989,15 +982,17 @@ void ReverbState::update(const ALCcontext *Context, const ALeffectslot *Slot, co
* gain.
*/
mParams.HFReference != props->Reverb.HFReference ||
- mParams.LFReference != props->Reverb.LFReference)
- mFadeCount = 0;
- mParams.Density = props->Reverb.Density;
- mParams.Diffusion = props->Reverb.Diffusion;
- mParams.DecayTime = props->Reverb.DecayTime;
- mParams.HFDecayTime = hfDecayTime;
- mParams.LFDecayTime = lfDecayTime;
- mParams.HFReference = props->Reverb.HFReference;
- mParams.LFReference = props->Reverb.LFReference;
+ mParams.LFReference != props->Reverb.LFReference);
+ if(mDoFading)
+ {
+ mParams.Density = props->Reverb.Density;
+ mParams.Diffusion = props->Reverb.Diffusion;
+ mParams.DecayTime = props->Reverb.DecayTime;
+ mParams.HFDecayTime = hfDecayTime;
+ mParams.LFDecayTime = lfDecayTime;
+ mParams.HFReference = props->Reverb.HFReference;
+ mParams.LFReference = props->Reverb.LFReference;
+ }
}
@@ -1056,8 +1051,7 @@ inline auto VectorPartialScatter(const std::array<float,NUM_LINES> &RESTRICT in,
/* Utilizes the above, but reverses the input channels. */
void VectorScatterRevDelayIn(const DelayLineI delay, size_t offset, const ALfloat xCoeff,
- const ALfloat yCoeff, const size_t base, const al::span<const ReverbUpdateLine,NUM_LINES> in,
- const size_t count)
+ const ALfloat yCoeff, const al::span<const ReverbUpdateLine,NUM_LINES> in, const size_t count)
{
ASSUME(count > 0);
@@ -1068,7 +1062,7 @@ void VectorScatterRevDelayIn(const DelayLineI delay, size_t offset, const ALfloa
do {
std::array<float,NUM_LINES> f;
for(size_t j{0u};j < NUM_LINES;j++)
- f[NUM_LINES-1-j] = in[j][base+i];
+ f[NUM_LINES-1-j] = in[j][i];
++i;
delay.Line[offset++] = VectorPartialScatter(f, xCoeff, yCoeff);
@@ -1125,14 +1119,14 @@ void VecAllpass::processUnfaded(const al::span<ReverbUpdateLine,NUM_LINES> sampl
}
}
void VecAllpass::processFaded(const al::span<ReverbUpdateLine,NUM_LINES> samples, size_t offset,
- const ALfloat xCoeff, const ALfloat yCoeff, ALfloat fade, const size_t todo)
+ const ALfloat xCoeff, const ALfloat yCoeff, ALfloat fadeCount, const ALfloat fadeStep,
+ const size_t todo)
{
const DelayLineI delay{Delay};
const ALfloat feedCoeff{Coeff};
ASSUME(todo > 0);
- fade *= 1.0f/FADE_SAMPLES;
size_t vap_offset[NUM_LINES][2];
for(size_t j{0u};j < NUM_LINES;j++)
{
@@ -1154,7 +1148,9 @@ void VecAllpass::processFaded(const al::span<ReverbUpdateLine,NUM_LINES> samples
size_t td{minz(delay.Mask+1 - maxoff, todo - i)};
do {
- fade += FadeStep;
+ fadeCount += 1.0f;
+ const float fade{fadeCount * fadeStep};
+
std::array<float,NUM_LINES> f;
for(size_t j{0u};j < NUM_LINES;j++)
f[j] = delay.Line[vap_offset[j][0]++][j]*(1.0f-fade) +
@@ -1194,7 +1190,7 @@ void VecAllpass::processFaded(const al::span<ReverbUpdateLine,NUM_LINES> samples
* Two static specializations are used for transitional (cross-faded) delay
* line processing and non-transitional processing.
*/
-void ReverbState::earlyUnfaded(const size_t offset, const size_t todo, const size_t base)
+void ReverbState::earlyUnfaded(const size_t offset, const size_t todo)
{
const al::span<ReverbUpdateLine,NUM_LINES> temps{mTempSamples};
const DelayLineI early_delay{mEarly.Delay};
@@ -1233,7 +1229,7 @@ void ReverbState::earlyUnfaded(const size_t offset, const size_t todo, const siz
{
size_t feedb_tap{offset - mEarly.Offset[j][0]};
const ALfloat feedb_coeff{mEarly.Coeff[j][0]};
- float *out = mEarlySamples[j].data() + base;
+ float *out = mEarlySamples[j].data();
for(size_t i{0u};i < todo;)
{
@@ -1253,9 +1249,10 @@ void ReverbState::earlyUnfaded(const size_t offset, const size_t todo, const siz
* bounce to improve the initial diffusion in the late reverb.
*/
const size_t late_feed_tap{offset - mLateFeedTap};
- VectorScatterRevDelayIn(main_delay, late_feed_tap, mixX, mixY, base, mEarlySamples, todo);
+ VectorScatterRevDelayIn(main_delay, late_feed_tap, mixX, mixY, mEarlySamples, todo);
}
-void ReverbState::earlyFaded(const size_t offset, const size_t todo, const ALfloat fade)
+void ReverbState::earlyFaded(const size_t offset, const size_t todo, const ALfloat fade,
+ const ALfloat fadeStep)
{
const al::span<ReverbUpdateLine,NUM_LINES> temps{mTempSamples};
const DelayLineI early_delay{mEarly.Delay};
@@ -1270,8 +1267,8 @@ void ReverbState::earlyFaded(const size_t offset, const size_t todo, const ALflo
size_t early_delay_tap0{offset - mEarlyDelayTap[j][0]};
size_t early_delay_tap1{offset - mEarlyDelayTap[j][1]};
const ALfloat oldCoeff{mEarlyDelayCoeff[j][0]};
- const ALfloat oldCoeffStep{-oldCoeff / FADE_SAMPLES};
- const ALfloat newCoeffStep{mEarlyDelayCoeff[j][1] / FADE_SAMPLES};
+ const ALfloat oldCoeffStep{-oldCoeff * fadeStep};
+ const ALfloat newCoeffStep{mEarlyDelayCoeff[j][1] * fadeStep};
ALfloat fadeCount{fade};
for(size_t i{0u};i < todo;)
@@ -1290,15 +1287,15 @@ void ReverbState::earlyFaded(const size_t offset, const size_t todo, const ALflo
}
}
- mEarly.VecAp.processFaded(temps, offset, mixX, mixY, fade, todo);
+ mEarly.VecAp.processFaded(temps, offset, mixX, mixY, fade, fadeStep, todo);
for(size_t j{0u};j < NUM_LINES;j++)
{
size_t feedb_tap0{offset - mEarly.Offset[j][0]};
size_t feedb_tap1{offset - mEarly.Offset[j][1]};
const ALfloat feedb_oldCoeff{mEarly.Coeff[j][0]};
- const ALfloat feedb_oldCoeffStep{-feedb_oldCoeff / FADE_SAMPLES};
- const ALfloat feedb_newCoeffStep{mEarly.Coeff[j][1] / FADE_SAMPLES};
+ const ALfloat feedb_oldCoeffStep{-feedb_oldCoeff * fadeStep};
+ const ALfloat feedb_newCoeffStep{mEarly.Coeff[j][1] * fadeStep};
float *out = mEarlySamples[j].data();
ALfloat fadeCount{fade};
@@ -1323,7 +1320,7 @@ void ReverbState::earlyFaded(const size_t offset, const size_t todo, const ALflo
early_delay.write(offset, NUM_LINES-1-j, temps[j].data(), todo);
const size_t late_feed_tap{offset - mLateFeedTap};
- VectorScatterRevDelayIn(main_delay, late_feed_tap, mixX, mixY, 0, mEarlySamples, todo);
+ VectorScatterRevDelayIn(main_delay, late_feed_tap, mixX, mixY, mEarlySamples, todo);
}
/* This generates the reverb tail using a modified feed-back delay network
@@ -1340,7 +1337,7 @@ void ReverbState::earlyFaded(const size_t offset, const size_t todo, const ALflo
* Two variations are made, one for for transitional (cross-faded) delay line
* processing and one for non-transitional processing.
*/
-void ReverbState::lateUnfaded(const size_t offset, const size_t todo, const size_t base)
+void ReverbState::lateUnfaded(const size_t offset, const size_t todo)
{
const al::span<ReverbUpdateLine,NUM_LINES> temps{mTempSamples};
const DelayLineI late_delay{mLate.Delay};
@@ -1379,12 +1376,13 @@ void ReverbState::lateUnfaded(const size_t offset, const size_t todo, const size
*/
mLate.VecAp.processUnfaded(temps, offset, mixX, mixY, todo);
for(size_t j{0u};j < NUM_LINES;j++)
- std::copy_n(temps[j].begin(), todo, mLateSamples[j].begin() + base);
+ std::copy_n(temps[j].begin(), todo, mLateSamples[j].begin());
/* Finally, scatter and bounce the results to refeed the feedback buffer. */
- VectorScatterRevDelayIn(late_delay, offset, mixX, mixY, 0, temps, todo);
+ VectorScatterRevDelayIn(late_delay, offset, mixX, mixY, temps, todo);
}
-void ReverbState::lateFaded(const size_t offset, const size_t todo, const ALfloat fade)
+void ReverbState::lateFaded(const size_t offset, const size_t todo, const ALfloat fade,
+ const ALfloat fadeStep)
{
const al::span<ReverbUpdateLine,NUM_LINES> temps{mTempSamples};
const DelayLineI late_delay{mLate.Delay};
@@ -1398,12 +1396,12 @@ void ReverbState::lateFaded(const size_t offset, const size_t todo, const ALfloa
{
const ALfloat oldMidGain{mLate.T60[j].MidGain[0]};
const ALfloat midGain{mLate.T60[j].MidGain[1]};
- const ALfloat oldMidStep{-oldMidGain / FADE_SAMPLES};
- const ALfloat midStep{midGain / FADE_SAMPLES};
+ const ALfloat oldMidStep{-oldMidGain * fadeStep};
+ const ALfloat midStep{midGain * fadeStep};
const ALfloat oldDensityGain{mLate.DensityGain[0] * oldMidGain};
const ALfloat densityGain{mLate.DensityGain[1] * midGain};
- const ALfloat oldDensityStep{-oldDensityGain / FADE_SAMPLES};
- const ALfloat densityStep{densityGain / FADE_SAMPLES};
+ const ALfloat oldDensityStep{-oldDensityGain * fadeStep};
+ const ALfloat densityStep{densityGain * fadeStep};
size_t late_delay_tap0{offset - mLateDelayTap[j][0]};
size_t late_delay_tap1{offset - mLateDelayTap[j][1]};
size_t late_feedb_tap0{offset - mLate.Offset[j][0]};
@@ -1435,17 +1433,16 @@ void ReverbState::lateFaded(const size_t offset, const size_t todo, const ALfloa
mLate.T60[j].process(temps[j].data(), todo);
}
- mLate.VecAp.processFaded(temps, offset, mixX, mixY, fade, todo);
+ mLate.VecAp.processFaded(temps, offset, mixX, mixY, fade, fadeStep, todo);
for(size_t j{0u};j < NUM_LINES;j++)
std::copy_n(temps[j].begin(), todo, mLateSamples[j].begin());
- VectorScatterRevDelayIn(late_delay, offset, mixX, mixY, 0, temps, todo);
+ VectorScatterRevDelayIn(late_delay, offset, mixX, mixY, temps, todo);
}
void ReverbState::process(const size_t samplesToDo, const FloatBufferLine *RESTRICT samplesIn, const ALsizei numInput, const al::span<FloatBufferLine> samplesOut)
{
size_t offset{mOffset};
- size_t fadeCount{mFadeCount};
ASSUME(samplesToDo > 0);
@@ -1463,67 +1460,68 @@ void ReverbState::process(const size_t samplesToDo, const FloatBufferLine *RESTR
}
/* Process reverb for these samples. */
- for(size_t base{0};base < samplesToDo;)
+ if LIKELY(!mDoFading)
{
- /* Calculate the number of samples we can do this iteration. */
- size_t todo{minz(samplesToDo - base, minz(mMaxUpdate[0], mMaxUpdate[1]))};
- /* Some mixers require maintaining a 4-sample alignment, so ensure that
- * if it's not the last iteration.
- */
- if(base+todo < samplesToDo) todo &= ~3;
- ASSUME(todo > 0);
+ for(size_t base{0};base < samplesToDo;)
+ {
+ /* Calculate the number of samples we can do this iteration. */
+ size_t todo{minz(samplesToDo - base, mMaxUpdate[0])};
+ /* Some mixers require maintaining a 4-sample alignment, so ensure
+ * that if it's not the last iteration.
+ */
+ if(base+todo < samplesToDo) todo &= ~3;
+ ASSUME(todo > 0);
+
+ /* Generate non-faded early reflections and late reverb. */
+ earlyUnfaded(offset, todo);
+ lateUnfaded(offset, todo);
- /* Process the samples for reverb. */
- size_t samples_done{0u};
- if UNLIKELY(fadeCount < FADE_SAMPLES)
+ /* Finally, mix early reflections and late reverb. */
+ (this->*mMixOut)(samplesOut, samplesToDo-base, base, todo);
+
+ offset += todo;
+ base += todo;
+ }
+ }
+ else
+ {
+ const float fadeStep{1.0f / static_cast<float>(samplesToDo)};
+ for(size_t base{0};base < samplesToDo;)
{
- /* If cross-fading, don't do more samples than there are to fade. */
- const size_t tofade{minz(todo, FADE_SAMPLES-fadeCount)};
- auto fade = static_cast<ALfloat>(fadeCount);
+ size_t todo{minz(samplesToDo - base, minz(mMaxUpdate[0], mMaxUpdate[1]))};
+ if(base+todo < samplesToDo) todo &= ~3;
+ ASSUME(todo > 0);
/* Generate cross-faded early reflections and late reverb. */
- earlyFaded(offset, tofade, fade);
- lateFaded(offset, tofade, fade);
-
- /* Step forward by amount faded. */
- samples_done += tofade;
- offset += tofade;
- fadeCount += tofade;
- if(fadeCount == FADE_SAMPLES)
- {
- /* Update the cross-fading delay line taps. */
- for(size_t c{0u};c < NUM_LINES;c++)
- {
- mEarlyDelayTap[c][0] = mEarlyDelayTap[c][1];
- mEarlyDelayCoeff[c][0] = mEarlyDelayCoeff[c][1];
- mEarly.VecAp.Offset[c][0] = mEarly.VecAp.Offset[c][1];
- mEarly.Offset[c][0] = mEarly.Offset[c][1];
- mEarly.Coeff[c][0] = mEarly.Coeff[c][1];
- mLateDelayTap[c][0] = mLateDelayTap[c][1];
- mLate.VecAp.Offset[c][0] = mLate.VecAp.Offset[c][1];
- mLate.Offset[c][0] = mLate.Offset[c][1];
- mLate.T60[c].MidGain[0] = mLate.T60[c].MidGain[1];
- }
- mLate.DensityGain[0] = mLate.DensityGain[1];
- mMaxUpdate[0] = mMaxUpdate[1];
- }
+ auto fadeCount = static_cast<ALfloat>(base);
+ earlyFaded(offset, todo, fadeCount, fadeStep);
+ lateFaded(offset, todo, fadeCount, fadeStep);
+
+ /* Finally, mix early reflections and late reverb. */
+ (this->*mMixOut)(samplesOut, samplesToDo-base, base, todo);
+
+ offset += todo;
+ base += todo;
}
- if LIKELY(samples_done < todo)
+
+ /* Update the cross-fading delay line taps. */
+ for(size_t c{0u};c < NUM_LINES;c++)
{
- /* Generate non-faded early reflections and late reverb. */
- const size_t remaining{todo - samples_done};
- earlyUnfaded(offset, remaining, samples_done);
- lateUnfaded(offset, remaining, samples_done);
- offset += remaining;
+ mEarlyDelayTap[c][0] = mEarlyDelayTap[c][1];
+ mEarlyDelayCoeff[c][0] = mEarlyDelayCoeff[c][1];
+ mEarly.VecAp.Offset[c][0] = mEarly.VecAp.Offset[c][1];
+ mEarly.Offset[c][0] = mEarly.Offset[c][1];
+ mEarly.Coeff[c][0] = mEarly.Coeff[c][1];
+ mLateDelayTap[c][0] = mLateDelayTap[c][1];
+ mLate.VecAp.Offset[c][0] = mLate.VecAp.Offset[c][1];
+ mLate.Offset[c][0] = mLate.Offset[c][1];
+ mLate.T60[c].MidGain[0] = mLate.T60[c].MidGain[1];
}
-
- /* Finally, mix early reflections and late reverb. */
- (this->*mMixOut)(samplesOut, samplesToDo-base, base, todo);
-
- base += todo;
+ mLate.DensityGain[0] = mLate.DensityGain[1];
+ mMaxUpdate[0] = mMaxUpdate[1];
+ mDoFading = false;
}
mOffset = offset;
- mFadeCount = fadeCount;
}