diff options
author | Chris Robinson <[email protected]> | 2022-12-17 01:10:02 -0800 |
---|---|---|
committer | Chris Robinson <[email protected]> | 2022-12-17 01:10:02 -0800 |
commit | ab19694f6027303ad548e15998dd6d18437fad7b (patch) | |
tree | 34f7ace76ae5d02ce0a9590c12a5a23bc7e2935f | |
parent | d281ffa9ce3abd8dfd5afd72914acebe4a4a92ff (diff) |
Rework reverb fading to toggle between pipelines
When non-simple properties are changed, the active reverb pipeline is switched
and the new parameters set on that one. The main process function will then be
set to fade out input on the old pipeline, fade in input on the new pipeline,
then process and mix both pipelines. After some number of samples (calculated
from its decay time), the old pipeline will stop processing.
This should improve the transition from a highly reverberant environment by not
harshly interpolating to the new environment, as well as better handle changes
to the all-pass and T60 filters.
-rw-r--r-- | alc/effects/reverb.cpp | 835 |
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; } |