diff options
Diffstat (limited to 'alc/effects/reverb.cpp')
-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; } |