aboutsummaryrefslogtreecommitdiffstats
path: root/alc/effects/reverb.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'alc/effects/reverb.cpp')
-rw-r--r--alc/effects/reverb.cpp835
1 files changed, 360 insertions, 475 deletions
diff --git a/alc/effects/reverb.cpp b/alc/effects/reverb.cpp
index 4af9e2a8..95dce5a9 100644
--- a/alc/effects/reverb.cpp
+++ b/alc/effects/reverb.cpp
@@ -302,12 +302,9 @@ struct DelayLineI {
struct VecAllpass {
DelayLineI Delay;
float Coeff{0.0f};
- size_t Offset[NUM_LINES][2]{};
+ size_t Offset[NUM_LINES]{};
- void processFaded(const al::span<ReverbUpdateLine,NUM_LINES> samples, size_t offset,
- const float xCoeff, const float yCoeff, float fadeCount, const float fadeStep,
- const size_t todo);
- void processUnfaded(const al::span<ReverbUpdateLine,NUM_LINES> samples, size_t offset,
+ void process(const al::span<ReverbUpdateLine,NUM_LINES> samples, size_t offset,
const float xCoeff, const float yCoeff, const size_t todo);
};
@@ -315,7 +312,7 @@ struct T60Filter {
/* Two filters are used to adjust the signal. One to control the low
* frequencies, and one to control the high frequencies.
*/
- float MidGain[2]{0.0f, 0.0f};
+ float MidGain{0.0f};
BiquadFilter HFFilter, LFFilter;
void calcCoeffs(const float length, const float lfDecayTime, const float mfDecayTime,
@@ -336,8 +333,8 @@ struct EarlyReflections {
* reflections.
*/
DelayLineI Delay;
- size_t Offset[NUM_LINES][2]{};
- float Coeff[NUM_LINES][2]{};
+ size_t Offset[NUM_LINES]{};
+ float Coeff[NUM_LINES]{};
/* The gain for each output channel based on 3D panning. */
float CurrentGain[NUM_LINES][MaxAmbiChannels]{};
@@ -355,25 +352,24 @@ struct Modulation {
uint Index, Step;
/* The depth of frequency change, in samples. */
- float Depth[2];
+ float Depth;
float ModDelays[MAX_UPDATE_SAMPLES];
void updateModulator(float modTime, float modDepth, float frequency);
void calcDelays(size_t todo);
- void calcFadedDelays(size_t todo, float fadeCount, float fadeStep);
};
struct LateReverb {
/* A recursive delay line is used fill in the reverb tail. */
DelayLineI Delay;
- size_t Offset[NUM_LINES][2]{};
+ size_t Offset[NUM_LINES]{};
/* Attenuation to compensate for the modal density and decay rate of the
* late lines.
*/
- float DensityGain[2]{0.0f, 0.0f};
+ float DensityGain{0.0f};
/* T60 decay filters are used to simulate absorption. */
T60Filter T60[NUM_LINES];
@@ -392,27 +388,7 @@ struct LateReverb {
const float hf0norm, const float frequency);
};
-struct ReverbState final : public EffectState {
- /* All delay lines are allocated as a single buffer to reduce memory
- * fragmentation and management code.
- */
- al::vector<std::array<float,NUM_LINES>,16> mSampleBuffer;
-
- struct {
- /* Calculated parameters which indicate if cross-fading is needed after
- * an update.
- */
- float Density{1.0f};
- float Diffusion{1.0f};
- float DecayTime{1.49f};
- float HFDecayTime{0.83f * 1.49f};
- float LFDecayTime{1.0f * 1.49f};
- float ModulationTime{0.25f};
- float ModulationDepth{0.0f};
- float HFReference{5000.0f};
- float LFReference{250.0f};
- } mParams;
-
+struct ReverbPipeline {
/* Master effect filters */
struct {
BiquadFilter Lp;
@@ -425,7 +401,7 @@ struct ReverbState final : public EffectState {
/* Tap points for early reflection delay. */
size_t mEarlyDelayTap[NUM_LINES][2]{};
- float mEarlyDelayCoeff[NUM_LINES][2]{};
+ float mEarlyDelayCoeff[NUM_LINES]{};
/* Tap points for late reverb feed and delay. */
size_t mLateDelayTap[NUM_LINES][2]{};
@@ -438,7 +414,56 @@ struct ReverbState final : public EffectState {
LateReverb mLate;
- bool mDoFading{};
+ std::array<std::array<BandSplitter,NUM_LINES>,2> mAmbiSplitter;
+
+ uint mFadeSampleCount{1};
+
+ void updateDelayLine(const float earlyDelay, const float lateDelay, const float density_mult,
+ const float decayTime, const float frequency);
+ void update3DPanning(const float *ReflectionsPan, const float *LateReverbPan,
+ const float earlyGain, const float lateGain, const bool doUpmix,
+ const EffectTarget &target);
+
+ void processEarly(size_t offset, const size_t samplesToDo,
+ const al::span<ReverbUpdateLine,NUM_LINES> tempSamples,
+ const al::span<FloatBufferLine,NUM_LINES> outSamples);
+ void processLate(size_t offset, const size_t samplesToDo,
+ const al::span<ReverbUpdateLine,NUM_LINES> tempSamples,
+ const al::span<FloatBufferLine,NUM_LINES> outSamples);
+};
+
+struct ReverbState final : public EffectState {
+ /* All delay lines are allocated as a single buffer to reduce memory
+ * fragmentation and management code.
+ */
+ al::vector<std::array<float,NUM_LINES>,16> mSampleBuffer;
+
+ struct {
+ /* Calculated parameters which indicate if cross-fading is needed after
+ * an update.
+ */
+ float Density{1.0f};
+ float Diffusion{1.0f};
+ float DecayTime{1.49f};
+ float HFDecayTime{0.83f * 1.49f};
+ float LFDecayTime{1.0f * 1.49f};
+ float ModulationTime{0.25f};
+ float ModulationDepth{0.0f};
+ float HFReference{5000.0f};
+ float LFReference{250.0f};
+ } mParams;
+
+ enum PipelineState : uint8_t {
+ DeviceClear,
+ StartFade,
+ Fading,
+ Cleanup,
+ Normal,
+ };
+ PipelineState mPipelineState{DeviceClear};
+ uint8_t mCurrentPipeline{0};
+
+ ReverbPipeline mPipelines[2];
/* The current write offset for all delay lines. */
size_t mOffset{};
@@ -451,10 +476,9 @@ struct ReverbState final : public EffectState {
alignas(16) std::array<FloatBufferLine,NUM_LINES> mEarlySamples{};
alignas(16) std::array<FloatBufferLine,NUM_LINES> mLateSamples{};
+ std::array<float,MaxAmbiOrder+1> mOrderScales{};
bool mUpmixOutput{false};
- std::array<float,MaxAmbiOrder+1> mOrderScales{};
- std::array<std::array<BandSplitter,NUM_LINES>,2> mAmbiSplitter;
static void DoMixRow(const al::span<float> OutBuffer, const al::span<const float,4> Gains,
@@ -478,7 +502,8 @@ struct ReverbState final : public EffectState {
}
- void MixOutPlain(const al::span<FloatBufferLine> samplesOut, const size_t todo)
+ void MixOutPlain(ReverbPipeline &pipeline, const al::span<FloatBufferLine> samplesOut,
+ const size_t todo)
{
ASSUME(todo > 0);
@@ -488,16 +513,19 @@ struct ReverbState final : public EffectState {
for(size_t c{0u};c < NUM_LINES;c++)
{
const al::span<float> tmpspan{mEarlySamples[c].data(), todo};
- MixSamples(tmpspan, samplesOut, mEarly.CurrentGain[c], mEarly.PanGain[c], todo, 0);
+ MixSamples(tmpspan, samplesOut, pipeline.mEarly.CurrentGain[c],
+ pipeline.mEarly.PanGain[c], todo, 0);
}
for(size_t c{0u};c < NUM_LINES;c++)
{
const al::span<float> tmpspan{mLateSamples[c].data(), todo};
- MixSamples(tmpspan, samplesOut, mLate.CurrentGain[c], mLate.PanGain[c], todo, 0);
+ MixSamples(tmpspan, samplesOut, pipeline.mLate.CurrentGain[c],
+ pipeline.mLate.PanGain[c], todo, 0);
}
}
- void MixOutAmbiUp(const al::span<FloatBufferLine> samplesOut, const size_t todo)
+ void MixOutAmbiUp(ReverbPipeline &pipeline, const al::span<FloatBufferLine> samplesOut,
+ const size_t todo)
{
ASSUME(todo > 0);
@@ -514,42 +542,33 @@ struct ReverbState final : public EffectState {
* higher-order output.
*/
const float hfscale{(c==0) ? mOrderScales[0] : mOrderScales[1]};
- mAmbiSplitter[0][c].processHfScale(tmpspan, hfscale);
+ pipeline.mAmbiSplitter[0][c].processHfScale(tmpspan, hfscale);
- MixSamples(tmpspan, samplesOut, mEarly.CurrentGain[c], mEarly.PanGain[c], todo, 0);
+ MixSamples(tmpspan, samplesOut, pipeline.mEarly.CurrentGain[c],
+ pipeline.mEarly.PanGain[c], todo, 0);
}
for(size_t c{0u};c < NUM_LINES;c++)
{
DoMixRow(tmpspan, LateA2B[c], mLateSamples[0].data(), mLateSamples[0].size());
const float hfscale{(c==0) ? mOrderScales[0] : mOrderScales[1]};
- mAmbiSplitter[1][c].processHfScale(tmpspan, hfscale);
+ pipeline.mAmbiSplitter[1][c].processHfScale(tmpspan, hfscale);
- MixSamples(tmpspan, samplesOut, mLate.CurrentGain[c], mLate.PanGain[c], todo, 0);
+ MixSamples(tmpspan, samplesOut, pipeline.mLate.CurrentGain[c],
+ pipeline.mLate.PanGain[c], todo, 0);
}
}
- void mixOut(const al::span<FloatBufferLine> samplesOut, const size_t todo)
+ void mixOut(ReverbPipeline &pipeline, const al::span<FloatBufferLine> samplesOut, const size_t todo)
{
if(mUpmixOutput)
- MixOutAmbiUp(samplesOut, todo);
+ MixOutAmbiUp(pipeline, samplesOut, todo);
else
- MixOutPlain(samplesOut, todo);
+ MixOutPlain(pipeline, samplesOut, todo);
}
void allocLines(const float frequency);
- void updateDelayLine(const float earlyDelay, const float lateDelay, const float density_mult,
- const float decayTime, const float frequency);
- void update3DPanning(const float *ReflectionsPan, const float *LateReverbPan,
- const float earlyGain, const float lateGain, const EffectTarget &target);
-
- void earlyUnfaded(size_t offset, const size_t samplesToDo);
- void earlyFaded(size_t offset, const size_t samplesToDo, const float fadeStep);
-
- void lateUnfaded(size_t offset, const size_t samplesToDo);
- void lateFaded(size_t offset, const size_t samplesToDo, const float fadeStep);
-
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
const EffectTarget target) override;
@@ -581,44 +600,51 @@ void ReverbState::allocLines(const float frequency)
*/
const float multiplier{CalcDelayLengthMult(1.0f)};
- /* 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
- * update size (BufferLineSize) for block processing.
- */
- float length{ReverbMaxReflectionsDelay + EARLY_TAP_LENGTHS.back()*multiplier};
- totalSamples += mEarlyDelayIn.calcLineLength(length, totalSamples, frequency, BufferLineSize);
-
- constexpr float LateLineDiffAvg{(LATE_LINE_LENGTHS.back()-LATE_LINE_LENGTHS.front()) /
- float{NUM_LINES}};
- length = ReverbMaxLateReverbDelay + LateLineDiffAvg*multiplier;
- totalSamples += mLateDelayIn.calcLineLength(length, totalSamples, frequency, BufferLineSize);
-
- /* The early vector all-pass line. */
- length = EARLY_ALLPASS_LENGTHS.back() * multiplier;
- totalSamples += mEarly.VecAp.Delay.calcLineLength(length, totalSamples, frequency, 0);
-
- /* The early reflection line. */
- length = EARLY_LINE_LENGTHS.back() * multiplier;
- totalSamples += mEarly.Delay.calcLineLength(length, totalSamples, frequency,
- MAX_UPDATE_SAMPLES);
-
- /* The late vector all-pass line. */
- length = LATE_ALLPASS_LENGTHS.back() * multiplier;
- totalSamples += mLate.VecAp.Delay.calcLineLength(length, totalSamples, frequency, 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.
*/
constexpr float max_mod_delay{MaxModulationTime*MODULATION_DEPTH_COEFF / 2.0f};
- /* The late delay lines are calculated from the largest maximum density
- * line length, and the maximum modulation delay. An additional sample is
- * added to keep it stable when there is no modulation.
- */
- length = LATE_LINE_LENGTHS.back()*multiplier + max_mod_delay;
- totalSamples += mLate.Delay.calcLineLength(length, totalSamples, frequency, 1);
+ for(auto &pipeline : mPipelines)
+ {
+ /* 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
+ * update size (BufferLineSize) for block processing.
+ */
+ float length{ReverbMaxReflectionsDelay + EARLY_TAP_LENGTHS.back()*multiplier};
+ totalSamples += pipeline.mEarlyDelayIn.calcLineLength(length, totalSamples, frequency,
+ BufferLineSize);
+
+ constexpr float LateLineDiffAvg{(LATE_LINE_LENGTHS.back()-LATE_LINE_LENGTHS.front()) /
+ float{NUM_LINES}};
+ length = ReverbMaxLateReverbDelay + LateLineDiffAvg*multiplier;
+ totalSamples += pipeline.mLateDelayIn.calcLineLength(length, totalSamples, frequency,
+ BufferLineSize);
+
+ /* The early vector all-pass line. */
+ length = EARLY_ALLPASS_LENGTHS.back() * multiplier;
+ totalSamples += pipeline.mEarly.VecAp.Delay.calcLineLength(length, totalSamples, frequency,
+ 0);
+
+ /* The early reflection line. */
+ length = EARLY_LINE_LENGTHS.back() * multiplier;
+ totalSamples += pipeline.mEarly.Delay.calcLineLength(length, totalSamples, frequency,
+ MAX_UPDATE_SAMPLES);
+
+ /* The late vector all-pass line. */
+ length = LATE_ALLPASS_LENGTHS.back() * multiplier;
+ totalSamples += pipeline.mLate.VecAp.Delay.calcLineLength(length, totalSamples, frequency,
+ 0);
+
+ /* The late delay lines are calculated from the largest maximum density
+ * line length, and the maximum modulation delay. An additional sample is
+ * added to keep it stable when there is no modulation.
+ */
+ length = LATE_LINE_LENGTHS.back()*multiplier + max_mod_delay;
+ totalSamples += pipeline.mLate.Delay.calcLineLength(length, totalSamples, frequency, 1);
+ }
if(totalSamples != mSampleBuffer.size())
decltype(mSampleBuffer)(totalSamples).swap(mSampleBuffer);
@@ -627,12 +653,15 @@ void ReverbState::allocLines(const float frequency)
std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), decltype(mSampleBuffer)::value_type{});
/* Update all delays to reflect the new sample buffer. */
- mEarlyDelayIn.realizeLineOffset(mSampleBuffer.data());
- mLateDelayIn.realizeLineOffset(mSampleBuffer.data());
- mEarly.VecAp.Delay.realizeLineOffset(mSampleBuffer.data());
- mEarly.Delay.realizeLineOffset(mSampleBuffer.data());
- mLate.VecAp.Delay.realizeLineOffset(mSampleBuffer.data());
- mLate.Delay.realizeLineOffset(mSampleBuffer.data());
+ for(auto &pipeline : mPipelines)
+ {
+ pipeline.mEarlyDelayIn.realizeLineOffset(mSampleBuffer.data());
+ pipeline.mLateDelayIn.realizeLineOffset(mSampleBuffer.data());
+ pipeline.mEarly.VecAp.Delay.realizeLineOffset(mSampleBuffer.data());
+ pipeline.mEarly.Delay.realizeLineOffset(mSampleBuffer.data());
+ pipeline.mLate.VecAp.Delay.realizeLineOffset(mSampleBuffer.data());
+ pipeline.mLate.Delay.realizeLineOffset(mSampleBuffer.data());
+ }
}
void ReverbState::deviceUpdate(const DeviceBase *device, const Buffer&)
@@ -642,45 +671,44 @@ void ReverbState::deviceUpdate(const DeviceBase *device, const Buffer&)
/* Allocate the delay lines. */
allocLines(frequency);
- /* Clear filters and gain coefficients since the delay lines were all just
- * cleared (if not reallocated).
- */
- for(auto &filter : mFilter)
+ for(auto &pipeline : mPipelines)
{
- filter.Lp.clear();
- filter.Hp.clear();
- }
+ /* Clear filters and gain coefficients since the delay lines were all just
+ * cleared (if not reallocated).
+ */
+ for(auto &filter : pipeline.mFilter)
+ {
+ filter.Lp.clear();
+ filter.Hp.clear();
+ }
- for(auto &coeff : mEarlyDelayCoeff)
- std::fill(std::begin(coeff), std::end(coeff), 0.0f);
- for(auto &coeff : mEarly.Coeff)
- std::fill(std::begin(coeff), std::end(coeff), 0.0f);
+ std::fill(std::begin(pipeline.mEarlyDelayCoeff),std::end(pipeline.mEarlyDelayCoeff), 0.0f);
+ std::fill(std::begin(pipeline.mEarlyDelayCoeff),std::end(pipeline.mEarlyDelayCoeff), 0.0f);
- mLate.DensityGain[0] = 0.0f;
- mLate.DensityGain[1] = 0.0f;
- for(auto &t60 : mLate.T60)
- {
- t60.MidGain[0] = 0.0f;
- t60.MidGain[1] = 0.0f;
- t60.HFFilter.clear();
- t60.LFFilter.clear();
+ pipeline.mLate.DensityGain = 0.0f;
+ for(auto &t60 : pipeline.mLate.T60)
+ {
+ t60.MidGain = 0.0f;
+ t60.HFFilter.clear();
+ t60.LFFilter.clear();
+ }
+
+ pipeline.mLate.Mod.Index = 0;
+ pipeline.mLate.Mod.Step = 1;
+ pipeline.mLate.Mod.Depth = 0.0f;
+
+ for(auto &gains : pipeline.mEarly.CurrentGain)
+ std::fill(std::begin(gains), std::end(gains), 0.0f);
+ for(auto &gains : pipeline.mEarly.PanGain)
+ std::fill(std::begin(gains), std::end(gains), 0.0f);
+ for(auto &gains : pipeline.mLate.CurrentGain)
+ std::fill(std::begin(gains), std::end(gains), 0.0f);
+ for(auto &gains : pipeline.mLate.PanGain)
+ std::fill(std::begin(gains), std::end(gains), 0.0f);
}
+ mPipelineState = DeviceClear;
- mLate.Mod.Index = 0;
- mLate.Mod.Step = 1;
- std::fill(std::begin(mLate.Mod.Depth), std::end(mLate.Mod.Depth), 0.0f);
-
- for(auto &gains : mEarly.CurrentGain)
- std::fill(std::begin(gains), std::end(gains), 0.0f);
- for(auto &gains : mEarly.PanGain)
- std::fill(std::begin(gains), std::end(gains), 0.0f);
- for(auto &gains : mLate.CurrentGain)
- std::fill(std::begin(gains), std::end(gains), 0.0f);
- for(auto &gains : mLate.PanGain)
- std::fill(std::begin(gains), std::end(gains), 0.0f);
-
- /* Reset fading and offset base. */
- mDoFading = true;
+ /* Reset offset base. */
mOffset = 0;
if(device->mAmbiOrder > 1)
@@ -693,9 +721,14 @@ void ReverbState::deviceUpdate(const DeviceBase *device, const Buffer&)
mUpmixOutput = false;
mOrderScales.fill(1.0f);
}
- mAmbiSplitter[0][0].init(device->mXOverFreq / frequency);
- std::fill(mAmbiSplitter[0].begin()+1, mAmbiSplitter[0].end(), mAmbiSplitter[0][0]);
- std::fill(mAmbiSplitter[1].begin(), mAmbiSplitter[1].end(), mAmbiSplitter[0][0]);
+ mPipelines[0].mAmbiSplitter[0][0].init(device->mXOverFreq / frequency);
+ for(auto &pipeline : mPipelines)
+ {
+ std::fill(pipeline.mAmbiSplitter[0].begin(), pipeline.mAmbiSplitter[0].end(),
+ pipeline.mAmbiSplitter[0][0]);
+ std::fill(pipeline.mAmbiSplitter[1].begin(), pipeline.mAmbiSplitter[1].end(),
+ pipeline.mAmbiSplitter[0][0]);
+ }
}
/**************************************
@@ -782,7 +815,7 @@ void T60Filter::calcCoeffs(const float length, const float lfDecayTime,
const float lfGain{CalcDecayCoeff(length, lfDecayTime) / mfGain};
const float hfGain{CalcDecayCoeff(length, hfDecayTime) / mfGain};
- MidGain[1] = mfGain;
+ MidGain = mfGain;
LFFilter.setParamsFromSlope(BiquadType::LowShelf, lf0norm, lfGain, 1.0f);
HFFilter.setParamsFromSlope(BiquadType::HighShelf, hf0norm, hfGain, 1.0f);
}
@@ -798,14 +831,14 @@ void EarlyReflections::updateLines(const float density_mult, const float diffusi
{
/* Calculate the delay length of each all-pass line. */
float length{EARLY_ALLPASS_LENGTHS[i] * density_mult};
- VecAp.Offset[i][1] = float2uint(length * frequency);
+ VecAp.Offset[i] = float2uint(length * frequency);
/* Calculate the delay length of each delay line. */
length = EARLY_LINE_LENGTHS[i] * density_mult;
- Offset[i][1] = float2uint(length * frequency);
+ Offset[i] = float2uint(length * frequency);
/* Calculate the gain (coefficient) for each line. */
- Coeff[i][1] = CalcDecayCoeff(length, decayTime);
+ Coeff[i] = CalcDecayCoeff(length, decayTime);
}
}
@@ -839,10 +872,10 @@ void Modulation::updateModulator(float modTime, float modDepth, float frequency)
* according to the modulation time. The natural form is varying
* inversely, in fact resulting in an invariant.
*/
- Depth[1] = MODULATION_DEPTH_COEFF / 4.0f * DefaultModulationTime * modDepth * frequency;
+ Depth = MODULATION_DEPTH_COEFF / 4.0f * DefaultModulationTime * modDepth * frequency;
}
else
- Depth[1] = MODULATION_DEPTH_COEFF / 4.0f * modTime * modDepth * frequency;
+ Depth = MODULATION_DEPTH_COEFF / 4.0f * modTime * modDepth * frequency;
}
/* Update the late reverb line lengths and T60 coefficients. */
@@ -879,7 +912,7 @@ void LateReverb::updateLines(const float density_mult, const float diffusion,
lf0norm*norm_weight_factor*lfDecayTime +
(hf0norm - lf0norm)*norm_weight_factor*mfDecayTime +
(1.0f - hf0norm*norm_weight_factor)*hfDecayTime};
- DensityGain[1] = CalcDensityGain(CalcDecayCoeff(length, decayTimeWeighted));
+ DensityGain = CalcDensityGain(CalcDecayCoeff(length, decayTimeWeighted));
/* Calculate the all-pass feed-back/forward coefficient. */
VecAp.Coeff = diffusion*diffusion * InvSqrt2;
@@ -888,11 +921,11 @@ void LateReverb::updateLines(const float density_mult, const float diffusion,
{
/* Calculate the delay length of each all-pass line. */
length = LATE_ALLPASS_LENGTHS[i] * density_mult;
- VecAp.Offset[i][1] = float2uint(length * frequency);
+ VecAp.Offset[i] = float2uint(length * frequency);
/* Calculate the delay length of each feedback delay line. */
length = LATE_LINE_LENGTHS[i] * density_mult;
- Offset[i][1] = float2uint(length*frequency + 0.5f);
+ Offset[i] = float2uint(length*frequency + 0.5f);
/* Approximate the absorption that the vector all-pass would exhibit
* given the current diffusion so we don't have to process a full T60
@@ -900,7 +933,7 @@ void LateReverb::updateLines(const float density_mult, const float diffusion,
* modulation delay (depth is half the max delay in samples).
*/
length += lerpf(LATE_ALLPASS_LENGTHS[i], late_allpass_avg, diffusion)*density_mult +
- Mod.Depth[1]/frequency;
+ Mod.Depth/frequency;
/* Calculate the T60 damping coefficients for each line. */
T60[i].calcCoeffs(length, lfDecayTime, mfDecayTime, hfDecayTime, lf0norm, hf0norm);
@@ -909,7 +942,7 @@ void LateReverb::updateLines(const float density_mult, const float diffusion,
/* Update the offsets for the main effect delay line. */
-void ReverbState::updateDelayLine(const float earlyDelay, const float lateDelay,
+void ReverbPipeline::updateDelayLine(const float earlyDelay, const float lateDelay,
const float density_mult, const float decayTime, const float frequency)
{
/* Early reflection taps are decorrelated by means of an average room
@@ -926,7 +959,7 @@ void ReverbState::updateDelayLine(const float earlyDelay, const float lateDelay,
{
float length{EARLY_TAP_LENGTHS[i]*density_mult};
mEarlyDelayTap[i][1] = float2uint((earlyDelay+length) * frequency);
- mEarlyDelayCoeff[i][1] = CalcDecayCoeff(length, decayTime);
+ mEarlyDelayCoeff[i] = CalcDecayCoeff(length, decayTime);
length = (LATE_LINE_LENGTHS[i] - LATE_LINE_LENGTHS.front())/float{NUM_LINES}*density_mult +
lateDelay;
@@ -977,8 +1010,8 @@ std::array<std::array<float,4>,4> GetTransformFromVector(const float *vec)
}
/* Update the early and late 3D panning gains. */
-void ReverbState::update3DPanning(const float *ReflectionsPan, const float *LateReverbPan,
- const float earlyGain, const float lateGain, const EffectTarget &target)
+void ReverbPipeline::update3DPanning(const float *ReflectionsPan, const float *LateReverbPan,
+ const float earlyGain, const float lateGain, const bool doUpmix, const EffectTarget &target)
{
/* Create matrices that transform a B-Format signal according to the
* panning vectors.
@@ -986,7 +1019,7 @@ void ReverbState::update3DPanning(const float *ReflectionsPan, const float *Late
const std::array<std::array<float,4>,4> earlymat{GetTransformFromVector(ReflectionsPan)};
const std::array<std::array<float,4>,4> latemat{GetTransformFromVector(LateReverbPan)};
- if(mUpmixOutput)
+ if(doUpmix)
{
/* When upsampling, combine the early and late transforms with the
* first-order upsample matrix. This results in panning gains that
@@ -1014,7 +1047,6 @@ void ReverbState::update3DPanning(const float *ReflectionsPan, const float *Late
auto earlycoeffs = mult_matrix(earlymat);
auto latecoeffs = mult_matrix(latemat);
- mOutTarget = target.Main->Buffer;
for(size_t i{0u};i < NUM_LINES;i++)
ComputePanGains(target.Main, earlycoeffs[i].data(), earlyGain, mEarly.PanGain[i]);
for(size_t i{0u};i < NUM_LINES;i++)
@@ -1047,7 +1079,6 @@ void ReverbState::update3DPanning(const float *ReflectionsPan, const float *Late
auto earlycoeffs = mult_matrix(EarlyA2B, earlymat);
auto latecoeffs = mult_matrix(LateA2B, latemat);
- mOutTarget = target.Main->Buffer;
for(size_t i{0u};i < NUM_LINES;i++)
ComputePanGains(target.Main, earlycoeffs[i].data(), earlyGain, mEarly.PanGain[i]);
for(size_t i{0u};i < NUM_LINES;i++)
@@ -1075,11 +1106,15 @@ void ReverbState::update(const ContextBase *Context, const EffectSlot *Slot,
MinDecayTime, MaxDecayTime)};
const float hfDecayTime{clampf(props->Reverb.DecayTime*hfRatio, MinDecayTime, MaxDecayTime)};
- /* Determine if delay-line cross-fading is required. Density is essentially
- * a master control for the feedback delays, so changes the offsets of many
- * delay lines.
+ /* Determine if a full update is required. Density is essentially a master
+ * control for the feedback delays, so changes the offsets of many delay
+ * lines.
*/
- mDoFading |= (mParams.Density != props->Reverb.Density ||
+ const bool fullUpdate{mPipelineState == DeviceClear ||
+ /* Density is essentially a master control for the feedback delays, so
+ * changes the offsets of many delay lines.
+ */
+ mParams.Density != props->Reverb.Density ||
/* Diffusion and decay times influences the decay rate (gain) of the
* late reverb T60 filter.
*/
@@ -1094,8 +1129,8 @@ void ReverbState::update(const ContextBase *Context, const EffectSlot *Slot,
* gain.
*/
mParams.HFReference != props->Reverb.HFReference ||
- mParams.LFReference != props->Reverb.LFReference);
- if(mDoFading)
+ mParams.LFReference != props->Reverb.LFReference};
+ if(fullUpdate)
{
mParams.Density = props->Reverb.Density;
mParams.Diffusion = props->Reverb.Diffusion;
@@ -1106,54 +1141,62 @@ void ReverbState::update(const ContextBase *Context, const EffectSlot *Slot,
mParams.ModulationDepth = props->Reverb.ModulationDepth;
mParams.HFReference = props->Reverb.HFReference;
mParams.LFReference = props->Reverb.LFReference;
- }
- /* Calculate the master filters */
- float hf0norm{minf(props->Reverb.HFReference/frequency, 0.49f)};
- mFilter[0].Lp.setParamsFromSlope(BiquadType::HighShelf, hf0norm, props->Reverb.GainHF, 1.0f);
- float lf0norm{minf(props->Reverb.LFReference/frequency, 0.49f)};
- mFilter[0].Hp.setParamsFromSlope(BiquadType::LowShelf, lf0norm, props->Reverb.GainLF, 1.0f);
- for(size_t i{1u};i < NUM_LINES;i++)
- {
- mFilter[i].Lp.copyParamsFrom(mFilter[0].Lp);
- mFilter[i].Hp.copyParamsFrom(mFilter[0].Hp);
+ mPipelineState = (mPipelineState != DeviceClear) ? StartFade : Normal;
+ mCurrentPipeline ^= 1;
}
+ auto &pipeline = mPipelines[mCurrentPipeline];
/* Update early and late 3D panning. */
+ mOutTarget = target.Main->Buffer;
const float gain{props->Reverb.Gain * Slot->Gain * ReverbBoost};
- update3DPanning(props->Reverb.ReflectionsPan, props->Reverb.LateReverbPan,
- props->Reverb.ReflectionsGain*gain, props->Reverb.LateReverbGain*gain, target);
+ pipeline.update3DPanning(props->Reverb.ReflectionsPan, props->Reverb.LateReverbPan,
+ props->Reverb.ReflectionsGain*gain, props->Reverb.LateReverbGain*gain, mUpmixOutput, target);
- if(!mDoFading)
+ if(!fullUpdate)
{
/* The density-based room size (delay length) multiplier. */
const float density_mult{CalcDelayLengthMult(mParams.Density)};
/* Update the main effect delay and associated taps. */
- updateDelayLine(props->Reverb.ReflectionsDelay, props->Reverb.LateReverbDelay,
+ pipeline.updateDelayLine(props->Reverb.ReflectionsDelay, props->Reverb.LateReverbDelay,
density_mult, mParams.DecayTime, frequency);
}
else
{
+ /* Calculate the master filters */
+ float hf0norm{minf(props->Reverb.HFReference/frequency, 0.49f)};
+ pipeline.mFilter[0].Lp.setParamsFromSlope(BiquadType::HighShelf, hf0norm, props->Reverb.GainHF, 1.0f);
+ float lf0norm{minf(props->Reverb.LFReference/frequency, 0.49f)};
+ pipeline.mFilter[0].Hp.setParamsFromSlope(BiquadType::LowShelf, lf0norm, props->Reverb.GainLF, 1.0f);
+ for(size_t i{1u};i < NUM_LINES;i++)
+ {
+ pipeline.mFilter[i].Lp.copyParamsFrom(pipeline.mFilter[0].Lp);
+ pipeline.mFilter[i].Hp.copyParamsFrom(pipeline.mFilter[0].Hp);
+ }
+
const float density_mult{CalcDelayLengthMult(props->Reverb.Density)};
- updateDelayLine(props->Reverb.ReflectionsDelay, props->Reverb.LateReverbDelay,
+ pipeline.updateDelayLine(props->Reverb.ReflectionsDelay, props->Reverb.LateReverbDelay,
density_mult, props->Reverb.DecayTime, frequency);
/* Update the early lines. */
- mEarly.updateLines(density_mult, props->Reverb.Diffusion, props->Reverb.DecayTime,
+ pipeline.mEarly.updateLines(density_mult, props->Reverb.Diffusion, props->Reverb.DecayTime,
frequency);
/* Get the mixing matrix coefficients. */
- CalcMatrixCoeffs(props->Reverb.Diffusion, &mMixX, &mMixY);
+ CalcMatrixCoeffs(props->Reverb.Diffusion, &pipeline.mMixX, &pipeline.mMixY);
/* Update the modulator rate and depth. */
- mLate.Mod.updateModulator(props->Reverb.ModulationTime, props->Reverb.ModulationDepth,
- frequency);
+ pipeline.mLate.Mod.updateModulator(props->Reverb.ModulationTime,
+ props->Reverb.ModulationDepth, frequency);
/* Update the late lines. */
- mLate.updateLines(density_mult, props->Reverb.Diffusion, lfDecayTime,
+ pipeline.mLate.updateLines(density_mult, props->Reverb.Diffusion, lfDecayTime,
props->Reverb.DecayTime, hfDecayTime, lf0norm, hf0norm, frequency);
+
+ const float decayCount{minf(props->Reverb.DecayTime*frequency, 1'000'000.0f)};
+ pipeline.mFadeSampleCount = static_cast<uint>(decayCount);
}
}
@@ -1242,7 +1285,7 @@ void VectorScatterRevDelayIn(const DelayLineI delay, size_t offset, const float
* Two static specializations are used for transitional (cross-faded) delay
* line processing and non-transitional processing.
*/
-void VecAllpass::processUnfaded(const al::span<ReverbUpdateLine,NUM_LINES> samples, size_t offset,
+void VecAllpass::process(const al::span<ReverbUpdateLine,NUM_LINES> samples, size_t offset,
const float xCoeff, const float yCoeff, const size_t todo)
{
const DelayLineI delay{Delay};
@@ -1252,7 +1295,7 @@ void VecAllpass::processUnfaded(const al::span<ReverbUpdateLine,NUM_LINES> sampl
size_t vap_offset[NUM_LINES];
for(size_t j{0u};j < NUM_LINES;j++)
- vap_offset[j] = offset - Offset[j][0];
+ vap_offset[j] = offset - Offset[j];
for(size_t i{0u};i < todo;)
{
for(size_t j{0u};j < NUM_LINES;j++)
@@ -1280,58 +1323,6 @@ void VecAllpass::processUnfaded(const al::span<ReverbUpdateLine,NUM_LINES> sampl
} while(--td);
}
}
-void VecAllpass::processFaded(const al::span<ReverbUpdateLine,NUM_LINES> samples, size_t offset,
- const float xCoeff, const float yCoeff, float fadeCount, const float fadeStep,
- const size_t todo)
-{
- const DelayLineI delay{Delay};
- const float feedCoeff{Coeff};
-
- ASSUME(todo > 0);
-
- size_t vap_offset[NUM_LINES][2];
- for(size_t j{0u};j < NUM_LINES;j++)
- {
- vap_offset[j][0] = offset - Offset[j][0];
- vap_offset[j][1] = offset - Offset[j][1];
- }
- for(size_t i{0u};i < todo;)
- {
- for(size_t j{0u};j < NUM_LINES;j++)
- {
- vap_offset[j][0] &= delay.Mask;
- vap_offset[j][1] &= delay.Mask;
- }
- offset &= delay.Mask;
-
- size_t maxoff{offset};
- for(size_t j{0u};j < NUM_LINES;j++)
- maxoff = maxz(maxoff, maxz(vap_offset[j][0], vap_offset[j][1]));
- size_t td{minz(delay.Mask+1 - maxoff, todo - i)};
-
- do {
- 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) +
- delay.Line[vap_offset[j][1]++][j]*fade;
-
- for(size_t j{0u};j < NUM_LINES;j++)
- {
- const float input{samples[j][i]};
- const float out{f[j] - feedCoeff*input};
- f[j] = input + feedCoeff*out;
-
- samples[j][i] = out;
- }
- ++i;
-
- delay.Line[offset++] = VectorPartialScatter(f, xCoeff, yCoeff);
- } while(--td);
- }
-}
/* This generates early reflections.
*
@@ -1348,11 +1339,10 @@ void VecAllpass::processFaded(const al::span<ReverbUpdateLine,NUM_LINES> samples
*
* Finally, the early response is reversed, scattered (based on diffusion),
* and fed into the late reverb section of the main delay line.
- *
- * Two static specializations are used for transitional (cross-faded) delay
- * line processing and non-transitional processing.
*/
-void ReverbState::earlyUnfaded(size_t offset, const size_t samplesToDo)
+void ReverbPipeline::processEarly(size_t offset, const size_t samplesToDo,
+ const al::span<ReverbUpdateLine, NUM_LINES> tempSamples,
+ const al::span<FloatBufferLine, NUM_LINES> outSamples)
{
const DelayLineI early_delay{mEarly.Delay};
const DelayLineI in_delay{mEarlyDelayIn};
@@ -1373,7 +1363,7 @@ void ReverbState::earlyUnfaded(size_t offset, const size_t samplesToDo)
{
size_t early_delay_tap0{offset - mEarlyDelayTap[j][0]};
size_t early_delay_tap1{offset - mEarlyDelayTap[j][1]};
- const float coeff{mEarlyDelayCoeff[j][0]};
+ const float coeff{mEarlyDelayCoeff[j]};
const float coeffStep{early_delay_tap0 != early_delay_tap1 ? coeff*fadeStep : 0.0f};
float fadeCount{0.0f};
@@ -1387,7 +1377,7 @@ void ReverbState::earlyUnfaded(size_t offset, const size_t samplesToDo)
const float fade0{coeff - coeffStep*fadeCount};
const float fade1{coeffStep*fadeCount};
fadeCount += 1.0f;
- mTempSamples[j][i++] = in_delay.Line[early_delay_tap0++][j]*fade0 +
+ tempSamples[j][i++] = in_delay.Line[early_delay_tap0++][j]*fade0 +
in_delay.Line[early_delay_tap1++][j]*fade1;
} while(--td);
}
@@ -1398,26 +1388,26 @@ void ReverbState::earlyUnfaded(size_t offset, const size_t samplesToDo)
/* Apply a vector all-pass, to help color the initial reflections based
* on the diffusion strength.
*/
- mEarly.VecAp.processUnfaded(mTempSamples, offset, mixX, mixY, todo);
+ mEarly.VecAp.process(tempSamples, offset, mixX, mixY, todo);
/* Apply a delay and bounce to generate secondary reflections, combine
* with the primary reflections and write out the result for mixing.
*/
for(size_t j{0u};j < NUM_LINES;j++)
- early_delay.write(offset, NUM_LINES-1-j, mTempSamples[j].data(), todo);
+ early_delay.write(offset, NUM_LINES-1-j, tempSamples[j].data(), todo);
for(size_t j{0u};j < NUM_LINES;j++)
{
- size_t feedb_tap{offset - mEarly.Offset[j][0]};
- const float feedb_coeff{mEarly.Coeff[j][0]};
- float *out{al::assume_aligned<16>(mEarlySamples[j].data() + base)};
+ size_t feedb_tap{offset - mEarly.Offset[j]};
+ const float feedb_coeff{mEarly.Coeff[j]};
+ float *RESTRICT out{al::assume_aligned<16>(outSamples[j].data() + base)};
for(size_t i{0u};i < todo;)
{
feedb_tap &= early_delay.Mask;
size_t td{minz(early_delay.Mask+1 - feedb_tap, todo - i)};
do {
- mTempSamples[j][i] += early_delay.Line[feedb_tap++][j]*feedb_coeff;
- out[i] = mTempSamples[j][i];
+ tempSamples[j][i] += early_delay.Line[feedb_tap++][j]*feedb_coeff;
+ out[i] = tempSamples[j][i];
++i;
} while(--td);
}
@@ -1427,97 +1417,19 @@ void ReverbState::earlyUnfaded(size_t offset, const size_t samplesToDo)
* reverb stage to pick up at the appropriate time, applying a scatter
* and bounce to improve the initial diffusion in the late reverb.
*/
- VectorScatterRevDelayIn(mLateDelayIn, offset, mixX, mixY, mTempSamples, todo);
+ VectorScatterRevDelayIn(mLateDelayIn, offset, mixX, mixY, tempSamples, todo);
base += todo;
offset += todo;
}
}
-void ReverbState::earlyFaded(size_t offset, const size_t samplesToDo, const float fadeStep)
-{
- const DelayLineI early_delay{mEarly.Delay};
- const DelayLineI in_delay{mEarlyDelayIn};
- const float mixX{mMixX};
- const float mixY{mMixY};
-
- ASSUME(samplesToDo > 0);
-
- for(size_t base{0};base < samplesToDo;)
- {
- const size_t todo{minz(samplesToDo-base, MAX_UPDATE_SAMPLES)};
- const float fade{static_cast<float>(base)};
-
- for(size_t j{0u};j < NUM_LINES;j++)
- {
- size_t early_delay_tap0{offset - mEarlyDelayTap[j][0]};
- size_t early_delay_tap1{offset - mEarlyDelayTap[j][1]};
- const float oldCoeff{mEarlyDelayCoeff[j][0]};
- const float oldCoeffStep{-oldCoeff * fadeStep};
- const float newCoeffStep{mEarlyDelayCoeff[j][1] * fadeStep};
- float fadeCount{fade};
-
- for(size_t i{0u};i < todo;)
- {
- early_delay_tap0 &= in_delay.Mask;
- early_delay_tap1 &= in_delay.Mask;
- const size_t max_tap{maxz(early_delay_tap0, early_delay_tap1)};
- size_t td{minz(in_delay.Mask+1 - max_tap, todo-i)};
- do {
- fadeCount += 1.0f;
- const float fade0{oldCoeff + oldCoeffStep*fadeCount};
- const float fade1{newCoeffStep*fadeCount};
- mTempSamples[j][i++] = in_delay.Line[early_delay_tap0++][j]*fade0 +
- in_delay.Line[early_delay_tap1++][j]*fade1;
- } while(--td);
- }
- }
-
- mEarly.VecAp.processFaded(mTempSamples, offset, mixX, mixY, fade, fadeStep, todo);
-
- for(size_t j{0u};j < NUM_LINES;j++)
- early_delay.write(offset, NUM_LINES-1-j, mTempSamples[j].data(), 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 float feedb_oldCoeff{mEarly.Coeff[j][0]};
- const float feedb_oldCoeffStep{-feedb_oldCoeff * fadeStep};
- const float feedb_newCoeffStep{mEarly.Coeff[j][1] * fadeStep};
- float *out{mEarlySamples[j].data() + base};
- float fadeCount{fade};
-
- for(size_t i{0u};i < todo;)
- {
- feedb_tap0 &= early_delay.Mask;
- feedb_tap1 &= early_delay.Mask;
- size_t td{minz(early_delay.Mask+1 - maxz(feedb_tap0, feedb_tap1), todo - i)};
-
- do {
- fadeCount += 1.0f;
- const float fade0{feedb_oldCoeff + feedb_oldCoeffStep*fadeCount};
- const float fade1{feedb_newCoeffStep*fadeCount};
- mTempSamples[j][i] += early_delay.Line[feedb_tap0++][j]*fade0 +
- early_delay.Line[feedb_tap1++][j]*fade1;
- out[i] = mTempSamples[j][i];
- ++i;
- } while(--td);
- }
- }
-
- VectorScatterRevDelayIn(mLateDelayIn, offset, mixX, mixY, mTempSamples, todo);
-
- base += todo;
- offset += todo;
- }
-}
-
void Modulation::calcDelays(size_t todo)
{
constexpr float mod_scale{al::numbers::pi_v<float> * 2.0f / MOD_FRACONE};
uint idx{Index};
const uint step{Step};
- const float depth{Depth[0]};
+ const float depth{Depth};
for(size_t i{0};i < todo;++i)
{
idx += step;
@@ -1527,23 +1439,6 @@ void Modulation::calcDelays(size_t todo)
Index = idx;
}
-void Modulation::calcFadedDelays(size_t todo, float fadeCount, float fadeStep)
-{
- constexpr float mod_scale{al::numbers::pi_v<float> * 2.0f / MOD_FRACONE};
- uint idx{Index};
- const uint step{Step};
- const float depth{Depth[0]};
- const float depthStep{(Depth[1]-depth) * fadeStep};
- for(size_t i{0};i < todo;++i)
- {
- fadeCount += 1.0f;
- idx += step;
- const float lfo{std::sin(static_cast<float>(idx&MOD_FRACMASK) * mod_scale)};
- ModDelays[i] = (lfo+1.0f) * (depth + depthStep*fadeCount);
- }
- Index = idx;
-}
-
/* This generates the reverb tail using a modified feed-back delay network
* (FDN).
@@ -1555,11 +1450,10 @@ void Modulation::calcFadedDelays(size_t todo, float fadeCount, float fadeStep)
*
* Finally, the lines are reversed (so they feed their opposite directions)
* and scattered with the FDN matrix before re-feeding the delay lines.
- *
- * Two variations are made, one for for transitional (cross-faded) delay line
- * processing and one for non-transitional processing.
*/
-void ReverbState::lateUnfaded(size_t offset, const size_t samplesToDo)
+void ReverbPipeline::processLate(size_t offset, const size_t samplesToDo,
+ const al::span<ReverbUpdateLine, NUM_LINES> tempSamples,
+ const al::span<FloatBufferLine, NUM_LINES> outSamples)
{
const DelayLineI late_delay{mLate.Delay};
const DelayLineI in_delay{mLateDelayIn};
@@ -1570,7 +1464,7 @@ void ReverbState::lateUnfaded(size_t offset, const size_t samplesToDo)
for(size_t base{0};base < samplesToDo;)
{
- const size_t todo{minz(samplesToDo-base, minz(mLate.Offset[0][0], MAX_UPDATE_SAMPLES))};
+ const size_t todo{minz(samplesToDo-base, minz(mLate.Offset[0], MAX_UPDATE_SAMPLES))};
ASSUME(todo > 0);
/* First, calculate the modulated delays for the late feedback. */
@@ -1584,9 +1478,9 @@ void ReverbState::lateUnfaded(size_t offset, const size_t samplesToDo)
{
size_t late_delay_tap0{offset - mLateDelayTap[j][0]};
size_t late_delay_tap1{offset - mLateDelayTap[j][1]};
- size_t late_feedb_tap{offset - mLate.Offset[j][0]};
- const float midGain{mLate.T60[j].MidGain[0]};
- const float densityGain{mLate.DensityGain[0] * midGain};
+ size_t late_feedb_tap{offset - mLate.Offset[j]};
+ const float midGain{mLate.T60[j].MidGain};
+ const float densityGain{mLate.DensityGain * midGain};
const float densityStep{late_delay_tap0 != late_delay_tap1 ?
densityGain*fadeStep : 0.0f};
float fadeCount{0.0f};
@@ -1616,7 +1510,7 @@ void ReverbState::lateUnfaded(size_t offset, const size_t samplesToDo)
const float fade0{densityGain - densityStep*fadeCount};
const float fade1{densityStep*fadeCount};
fadeCount += 1.0f;
- mTempSamples[j][i] = lerpf(out0, out1, frac)*midGain +
+ tempSamples[j][i] = lerpf(out0, out1, frac)*midGain +
in_delay.Line[late_delay_tap0++][j]*fade0 +
in_delay.Line[late_delay_tap1++][j]*fade1;
++i;
@@ -1624,172 +1518,163 @@ void ReverbState::lateUnfaded(size_t offset, const size_t samplesToDo)
}
mLateDelayTap[j][0] = mLateDelayTap[j][1];
- mLate.T60[j].process({mTempSamples[j].data(), todo});
+ mLate.T60[j].process({tempSamples[j].data(), todo});
}
/* Apply a vector all-pass to improve micro-surface diffusion, and
* write out the results for mixing.
*/
- mLate.VecAp.processUnfaded(mTempSamples, offset, mixX, mixY, todo);
+ mLate.VecAp.process(tempSamples, offset, mixX, mixY, todo);
for(size_t j{0u};j < NUM_LINES;j++)
- std::copy_n(mTempSamples[j].begin(), todo, mLateSamples[j].begin()+base);
+ std::copy_n(tempSamples[j].begin(), todo, outSamples[j].begin()+base);
/* Finally, scatter and bounce the results to refeed the feedback buffer. */
- VectorScatterRevDelayIn(late_delay, offset, mixX, mixY, mTempSamples, todo);
+ VectorScatterRevDelayIn(late_delay, offset, mixX, mixY, tempSamples, todo);
base += todo;
offset += todo;
}
}
-void ReverbState::lateFaded(size_t offset, const size_t samplesToDo, const float fadeStep)
+
+void ReverbState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
- const DelayLineI late_delay{mLate.Delay};
- const DelayLineI in_delay{mLateDelayIn};
- const float mixX{mMixX};
- const float mixY{mMixY};
+ const size_t offset{mOffset};
ASSUME(samplesToDo > 0);
- for(size_t base{0};base < samplesToDo;)
- {
- const size_t min_offset{mLate.Offset[0][0] ? minz(mLate.Offset[0][0], mLate.Offset[0][1])
- : mLate.Offset[0][1]};
- const size_t todo{minz(minz(samplesToDo-base, min_offset), MAX_UPDATE_SAMPLES)};
- ASSUME(todo > 0);
-
- const float fade{static_cast<float>(base)};
+ auto &oldpipeline = mPipelines[mCurrentPipeline^1];
+ auto &pipeline = mPipelines[mCurrentPipeline];
- mLate.Mod.calcFadedDelays(todo, fade, fadeStep);
-
- for(size_t j{0u};j < NUM_LINES;j++)
+ if(mPipelineState >= Fading)
+ {
+ /* Convert B-Format to A-Format for processing. */
+ const size_t numInput{minz(samplesIn.size(), NUM_LINES)};
+ const al::span<float> tmpspan{al::assume_aligned<16>(mTempLine.data()), samplesToDo};
+ for(size_t c{0u};c < NUM_LINES;c++)
{
- const float oldMidGain{mLate.T60[j].MidGain[0]};
- const float midGain{mLate.T60[j].MidGain[1]};
- const float oldMidStep{-oldMidGain * fadeStep};
- const float midStep{midGain * fadeStep};
- const float oldDensityGain{mLate.DensityGain[0] * oldMidGain};
- const float densityGain{mLate.DensityGain[1] * midGain};
- const float oldDensityStep{-oldDensityGain * fadeStep};
- const float 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]};
- size_t late_feedb_tap1{offset - mLate.Offset[j][1]};
- float fadeCount{fade};
-
- for(size_t i{0u};i < todo;)
+ std::fill(tmpspan.begin(), tmpspan.end(), 0.0f);
+ for(size_t i{0};i < numInput;++i)
{
- late_delay_tap0 &= in_delay.Mask;
- late_delay_tap1 &= in_delay.Mask;
- size_t td{minz(todo-i, in_delay.Mask+1 - maxz(late_delay_tap0, late_delay_tap1))};
- do {
- fadeCount += 1.0f;
+ const float gain{B2A[c][i]};
+ const float *RESTRICT input{al::assume_aligned<16>(samplesIn[i].data())};
- const float fdelay{mLate.Mod.ModDelays[i]};
- const size_t delay{float2uint(fdelay)};
- const float frac{fdelay - static_cast<float>(delay)};
+ for(float &sample : tmpspan)
+ {
+ sample += *input * gain;
+ ++input;
+ }
+ }
- const size_t late_mask{late_delay.Mask};
- const float out00{late_delay.Line[(late_feedb_tap0-delay) & late_mask][j]};
- const float out01{late_delay.Line[(late_feedb_tap0-delay-1) & late_mask][j]};
- ++late_feedb_tap0;
- const float out10{late_delay.Line[(late_feedb_tap1-delay) & late_mask][j]};
- const float out11{late_delay.Line[(late_feedb_tap1-delay-1) & late_mask][j]};
- ++late_feedb_tap1;
+ /* Band-pass the incoming samples and feed the initial delay line. */
+ auto&& filter = DualBiquad{pipeline.mFilter[c].Lp, pipeline.mFilter[c].Hp};
+ filter.process(tmpspan, tmpspan.data());
+ pipeline.mEarlyDelayIn.write(offset, c, tmpspan.cbegin(), samplesToDo);
+ }
+ if(mPipelineState == Fading)
+ {
+ /* Give the old pipeline silence if it's still fading out. */
+ for(size_t c{0u};c < NUM_LINES;c++)
+ {
+ std::fill(tmpspan.begin(), tmpspan.end(), 0.0f);
- const float fade0{oldDensityGain + oldDensityStep*fadeCount};
- const float fade1{densityStep*fadeCount};
- const float gfade0{oldMidGain + oldMidStep*fadeCount};
- const float gfade1{midStep*fadeCount};
- mTempSamples[j][i] = lerpf(out00, out01, frac)*gfade0 +
- lerpf(out10, out11, frac)*gfade1 +
- in_delay.Line[late_delay_tap0++][j]*fade0 +
- in_delay.Line[late_delay_tap1++][j]*fade1;
- ++i;
- } while(--td);
+ auto&& filter = DualBiquad{oldpipeline.mFilter[c].Lp, oldpipeline.mFilter[c].Hp};
+ filter.process(tmpspan, tmpspan.data());
+ oldpipeline.mEarlyDelayIn.write(offset, c, tmpspan.cbegin(), samplesToDo);
}
- mLate.T60[j].process({mTempSamples[j].data(), todo});
}
-
- mLate.VecAp.processFaded(mTempSamples, offset, mixX, mixY, fade, fadeStep, todo);
- for(size_t j{0u};j < NUM_LINES;j++)
- std::copy_n(mTempSamples[j].begin(), todo, mLateSamples[j].begin()+base);
-
- VectorScatterRevDelayIn(late_delay, offset, mixX, mixY, mTempSamples, todo);
-
- base += todo;
- offset += todo;
}
-}
+ else
+ {
+ /* At the start of a fade, fade in input for the current pipeline, and
+ * fade out input for the old pipeline.
+ */
+ const size_t numInput{minz(samplesIn.size(), NUM_LINES)};
+ const al::span<float> tmpspan{al::assume_aligned<16>(mTempLine.data()), samplesToDo};
+ const float fadeStep{1.0f / static_cast<float>(samplesToDo)};
-void ReverbState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
-{
- size_t offset{mOffset};
+ for(size_t c{0u};c < NUM_LINES;c++)
+ {
+ std::fill(tmpspan.begin(), tmpspan.end(), 0.0f);
+ for(size_t i{0};i < numInput;++i)
+ {
+ const float gain{B2A[c][i]};
+ const float *RESTRICT input{al::assume_aligned<16>(samplesIn[i].data())};
- ASSUME(samplesToDo > 0);
+ for(float &sample : tmpspan)
+ {
+ sample += *input * gain;
+ ++input;
+ }
+ }
+ float stepCount{0.0f};
+ for(size_t i{0};i < samplesToDo;++i)
+ {
+ stepCount += 1.0f;
+ tmpspan[i] *= stepCount*fadeStep;
+ }
- /* Convert B-Format to A-Format for processing. */
- const size_t numInput{minz(samplesIn.size(), NUM_LINES)};
- const al::span<float> tmpspan{al::assume_aligned<16>(mTempLine.data()), samplesToDo};
- for(size_t c{0u};c < NUM_LINES;c++)
- {
- std::fill(tmpspan.begin(), tmpspan.end(), 0.0f);
- for(size_t i{0};i < numInput;++i)
+ auto&& filter = DualBiquad{pipeline.mFilter[c].Lp, pipeline.mFilter[c].Hp};
+ filter.process(tmpspan, tmpspan.data());
+ pipeline.mEarlyDelayIn.write(offset, c, tmpspan.cbegin(), samplesToDo);
+ }
+ for(size_t c{0u};c < NUM_LINES;c++)
{
- const float gain{B2A[c][i]};
- const float *RESTRICT input{al::assume_aligned<16>(samplesIn[i].data())};
+ std::fill(tmpspan.begin(), tmpspan.end(), 0.0f);
+ for(size_t i{0};i < numInput;++i)
+ {
+ const float gain{B2A[c][i]};
+ const float *RESTRICT input{al::assume_aligned<16>(samplesIn[i].data())};
- for(float &sample : tmpspan)
+ for(float &sample : tmpspan)
+ {
+ sample += *input * gain;
+ ++input;
+ }
+ }
+ float stepCount{0.0f};
+ for(size_t i{0};i < samplesToDo;++i)
{
- sample += *input * gain;
- ++input;
+ stepCount += 1.0f;
+ tmpspan[i] *= 1.0f - stepCount*fadeStep;
}
- }
- /* Band-pass the incoming samples and feed the initial delay line. */
- DualBiquad{mFilter[c].Lp, mFilter[c].Hp}.process(tmpspan, tmpspan.data());
- mEarlyDelayIn.write(offset, c, tmpspan.cbegin(), samplesToDo);
+ auto&& filter = DualBiquad{oldpipeline.mFilter[c].Lp, oldpipeline.mFilter[c].Hp};
+ filter.process(tmpspan, tmpspan.data());
+ oldpipeline.mEarlyDelayIn.write(offset, c, tmpspan.cbegin(), samplesToDo);
+ }
+ mPipelineState = Fading;
}
- /* Process reverb for these samples. */
- if(!mDoFading) [[likely]]
- {
- /* Generate non-faded early reflections and late reverb. */
- earlyUnfaded(offset, samplesToDo);
- lateUnfaded(offset, samplesToDo);
+ /* Process reverb for these samples. and mix them to the output. */
+ pipeline.processEarly(offset, samplesToDo, mTempSamples, mEarlySamples);
+ pipeline.processLate(offset, samplesToDo, mTempSamples, mLateSamples);
+ mixOut(pipeline, samplesOut, samplesToDo);
- /* Finally, mix early reflections and late reverb. */
- mixOut(samplesOut, samplesToDo);
- }
- else
+ if(mPipelineState != Normal)
{
- const float fadeStep{1.0f / static_cast<float>(samplesToDo)};
-
- /* Generate cross-faded early reflections and late reverb. */
- earlyFaded(offset, samplesToDo, fadeStep);
- lateFaded(offset, samplesToDo, fadeStep);
-
- mixOut(samplesOut, samplesToDo);
-
-
- /* Update the cross-fading delay line taps. */
- for(size_t c{0u};c < NUM_LINES;c++)
+ if(mPipelineState == Cleanup)
+ {
+ /* TODO: Clear feedback buffers and filter history. */
+ mPipelineState = Normal;
+ }
+ else
{
- mEarlyDelayTap[c][0] = mEarlyDelayTap[c][1];
- mEarlyDelayCoeff[c][0] = mEarlyDelayCoeff[c][1];
- mLateDelayTap[c][0] = mLateDelayTap[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];
- mLate.Offset[c][0] = mLate.Offset[c][1];
- mLate.T60[c].MidGain[0] = mLate.T60[c].MidGain[1];
- mLate.VecAp.Offset[c][0] = mLate.VecAp.Offset[c][1];
+ /* Process the old reverb for these samples. */
+ oldpipeline.processEarly(offset, samplesToDo, mTempSamples, mEarlySamples);
+ oldpipeline.processLate(offset, samplesToDo, mTempSamples, mLateSamples);
+ mixOut(oldpipeline, samplesOut, samplesToDo);
+
+ if(samplesToDo >= oldpipeline.mFadeSampleCount)
+ {
+ oldpipeline.mFadeSampleCount = 0;
+ mPipelineState = Cleanup;
+ }
+ else
+ oldpipeline.mFadeSampleCount -= samplesToDo;
}
- mLate.DensityGain[0] = mLate.DensityGain[1];
- mLate.Mod.Depth[0] = mLate.Mod.Depth[1];
- mDoFading = false;
}
- mOffset += samplesToDo;
+
+ mOffset = offset + samplesToDo;
}